diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 26aec85659..056535074c 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -1,2363 +1,2365 @@
 <?php
 
 /**
  * This file is automatically generated. Use 'bin/celerity map' to rebuild it.
  *
  * @generated
  */
 return array(
   'names' => array(
     'conpherence.pkg.css' => '3c8a0668',
     'conpherence.pkg.js' => '020aebcf',
     'core.pkg.css' => '261ee8cf',
     'core.pkg.js' => '5ace8a1e',
-    'differential.pkg.css' => 'b8df73d4',
+    'differential.pkg.css' => 'c3f15714',
     'differential.pkg.js' => '67c9ea4c',
     'diffusion.pkg.css' => '42c75c37',
     'diffusion.pkg.js' => '91192d85',
     'maniphest.pkg.css' => '35995d6d',
     'maniphest.pkg.js' => '286955ae',
     'rsrc/audio/basic/alert.mp3' => '17889334',
     'rsrc/audio/basic/bing.mp3' => 'a817a0c3',
     'rsrc/audio/basic/pock.mp3' => '0fa843d0',
     'rsrc/audio/basic/tap.mp3' => '02d16994',
     'rsrc/audio/basic/ting.mp3' => 'a6b6540e',
     'rsrc/css/aphront/aphront-bars.css' => '4a327b4a',
     'rsrc/css/aphront/dark-console.css' => '7f06cda2',
     'rsrc/css/aphront/dialog-view.css' => 'b70c70df',
     'rsrc/css/aphront/list-filter-view.css' => 'feb64255',
     'rsrc/css/aphront/multi-column.css' => 'fbc00ba3',
     'rsrc/css/aphront/notification.css' => '30240bd2',
     'rsrc/css/aphront/panel-view.css' => '46923d46',
     'rsrc/css/aphront/phabricator-nav-view.css' => 'f8a0c1bf',
     'rsrc/css/aphront/table-view.css' => '205053cd',
     'rsrc/css/aphront/tokenizer.css' => 'b52d0668',
     'rsrc/css/aphront/tooltip.css' => 'e3f2412f',
     'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2',
     'rsrc/css/aphront/typeahead.css' => '8779483d',
     'rsrc/css/application/almanac/almanac.css' => '2e050f4f',
     'rsrc/css/application/auth/auth.css' => 'add92fd8',
     'rsrc/css/application/base/main-menu-view.css' => '8e2d9a28',
     'rsrc/css/application/base/notification-menu.css' => 'e6962e89',
     'rsrc/css/application/base/phui-theme.css' => '35883b37',
     'rsrc/css/application/base/standard-page-view.css' => '8a295cb9',
     'rsrc/css/application/chatlog/chatlog.css' => 'abdc76ee',
     'rsrc/css/application/conduit/conduit-api.css' => 'ce2cfc41',
     'rsrc/css/application/config/config-options.css' => '16c920ae',
     'rsrc/css/application/config/config-template.css' => '20babf50',
     'rsrc/css/application/config/setup-issue.css' => '5eed85b2',
     'rsrc/css/application/config/unhandled-exception.css' => '9ecfc00d',
     'rsrc/css/application/conpherence/color.css' => 'b17746b0',
     'rsrc/css/application/conpherence/durable-column.css' => '2d57072b',
     'rsrc/css/application/conpherence/header-pane.css' => 'c9a3db8e',
     'rsrc/css/application/conpherence/menu.css' => '67f4680d',
     'rsrc/css/application/conpherence/message-pane.css' => 'd244db1e',
     'rsrc/css/application/conpherence/notification.css' => '6a3d4e58',
     'rsrc/css/application/conpherence/participant-pane.css' => '69e0058a',
     'rsrc/css/application/conpherence/transaction.css' => '3a3f5e7e',
     'rsrc/css/application/contentsource/content-source-view.css' => 'cdf0d579',
     'rsrc/css/application/countdown/timer.css' => 'bff8012f',
     'rsrc/css/application/daemon/bulk-job.css' => '73af99f5',
     'rsrc/css/application/dashboard/dashboard.css' => '4267d6c6',
     'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
     'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
-    'rsrc/css/application/differential/changeset-view.css' => '73660575',
+    'rsrc/css/application/differential/changeset-view.css' => '783a9206',
     'rsrc/css/application/differential/core.css' => 'bdb93065',
     'rsrc/css/application/differential/phui-inline-comment.css' => '48acce5b',
     'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
     'rsrc/css/application/differential/revision-history.css' => '8aa3eac5',
     'rsrc/css/application/differential/revision-list.css' => '93d2df7d',
     'rsrc/css/application/differential/table-of-contents.css' => '0e3364c7',
     'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b',
     'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4',
     'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c',
     'rsrc/css/application/diffusion/diffusion.css' => 'b54c77b0',
     'rsrc/css/application/feed/feed.css' => 'd8b6e3f8',
     'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4',
     'rsrc/css/application/flag/flag.css' => '2b77be8d',
     'rsrc/css/application/harbormaster/harbormaster.css' => '8dfe16b2',
     'rsrc/css/application/herald/herald-test.css' => 'e004176f',
     'rsrc/css/application/herald/herald.css' => '648d39e2',
     'rsrc/css/application/maniphest/report.css' => '3d53188b',
     'rsrc/css/application/maniphest/task-edit.css' => '272daa84',
     'rsrc/css/application/maniphest/task-summary.css' => '61d1667e',
     'rsrc/css/application/objectselector/object-selector.css' => 'ee77366f',
     'rsrc/css/application/owners/owners-path-editor.css' => 'fa7c13ef',
     'rsrc/css/application/paste/paste.css' => 'b37bcd38',
     'rsrc/css/application/people/people-picture-menu-item.css' => 'fe8e07cf',
     'rsrc/css/application/people/people-profile.css' => '2ea2daa1',
     'rsrc/css/application/phame/phame.css' => '799febf9',
     'rsrc/css/application/pholio/pholio-edit.css' => '4df55b3b',
     'rsrc/css/application/pholio/pholio-inline-comments.css' => '722b48c2',
     'rsrc/css/application/pholio/pholio.css' => '88ef5ef1',
     'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8',
     'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241',
     'rsrc/css/application/phortune/phortune.css' => '12e8251a',
     'rsrc/css/application/phrequent/phrequent.css' => 'bd79cc67',
     'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0',
     'rsrc/css/application/policy/policy-edit.css' => '8794e2ed',
     'rsrc/css/application/policy/policy-transaction-detail.css' => 'c02b8384',
     'rsrc/css/application/policy/policy.css' => 'ceb56a08',
     'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a',
     'rsrc/css/application/project/project-card-view.css' => '3b1f7b20',
     'rsrc/css/application/project/project-view.css' => '567858b3',
     'rsrc/css/application/releeph/releeph-core.css' => 'f81ff2db',
     'rsrc/css/application/releeph/releeph-preview-branch.css' => '22db5c07',
     'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '0ac1ea31',
     'rsrc/css/application/releeph/releeph-request-typeahead.css' => 'bce37359',
     'rsrc/css/application/search/application-search-view.css' => '0f7c06d8',
     'rsrc/css/application/search/search-results.css' => '9ea70ace',
     'rsrc/css/application/slowvote/slowvote.css' => '1694baed',
     'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd',
     'rsrc/css/application/uiexample/example.css' => 'b4795059',
     'rsrc/css/core/core.css' => '1b29ed61',
     'rsrc/css/core/remarkup.css' => '9e627d41',
     'rsrc/css/core/syntax.css' => '8a16f91b',
     'rsrc/css/core/z-index.css' => '99c0f5eb',
     'rsrc/css/diviner/diviner-shared.css' => '4bd263b0',
     'rsrc/css/font/font-awesome.css' => '3883938a',
     'rsrc/css/font/font-lato.css' => '23631304',
     'rsrc/css/font/phui-font-icon-base.css' => 'd7994e06',
     'rsrc/css/layout/phabricator-filetree-view.css' => '56cdd875',
     'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28',
     'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4',
     'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa',
     'rsrc/css/phui/button/phui-button.css' => 'ea704902',
     'rsrc/css/phui/calendar/phui-calendar-day.css' => '9597d706',
     'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2',
     'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42',
     'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa',
     'rsrc/css/phui/object-item/phui-oi-big-ui.css' => '9e037c7a',
     'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0',
     'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
     'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
     'rsrc/css/phui/object-item/phui-oi-list-view.css' => '909f3844',
     'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46',
     'rsrc/css/phui/phui-action-list.css' => 'c4972757',
     'rsrc/css/phui/phui-action-panel.css' => '6c386cbf',
     'rsrc/css/phui/phui-badge.css' => '666e25ad',
     'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d',
     'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
     'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
     'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
     'rsrc/css/phui/phui-chart.css' => '7853a69b',
     'rsrc/css/phui/phui-cms.css' => '8c05c41e',
     'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
     'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
     'rsrc/css/phui/phui-crumbs-view.css' => '614f43cf',
     'rsrc/css/phui/phui-curtain-view.css' => '68c5efb6',
     'rsrc/css/phui/phui-document-pro.css' => 'b9613a10',
     'rsrc/css/phui/phui-document-summary.css' => 'b068eed1',
     'rsrc/css/phui/phui-document.css' => '52b748a5',
     'rsrc/css/phui/phui-feed-story.css' => 'a0c05029',
     'rsrc/css/phui/phui-fontkit.css' => '9b714a5e',
     'rsrc/css/phui/phui-form-view.css' => '01b796c0',
     'rsrc/css/phui/phui-form.css' => '159e2d9c',
     'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
     'rsrc/css/phui/phui-header-view.css' => '285c9139',
     'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
     'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
     'rsrc/css/phui/phui-icon.css' => '4cbc684a',
     'rsrc/css/phui/phui-image-mask.css' => '62c7f4d2',
     'rsrc/css/phui/phui-info-view.css' => '37b8d9ce',
     'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4',
     'rsrc/css/phui/phui-left-right.css' => '68513c34',
     'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
     'rsrc/css/phui/phui-list.css' => '470b1adb',
     'rsrc/css/phui/phui-object-box.css' => 'f434b6be',
     'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
     'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
     'rsrc/css/phui/phui-property-list-view.css' => 'cad62236',
     'rsrc/css/phui/phui-remarkup-preview.css' => '91767007',
     'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370',
     'rsrc/css/phui/phui-spacing.css' => 'b05cadc3',
     'rsrc/css/phui/phui-status.css' => 'e5ff8be0',
     'rsrc/css/phui/phui-tag-view.css' => '29409667',
     'rsrc/css/phui/phui-timeline-view.css' => '1e348e4b',
     'rsrc/css/phui/phui-two-column-view.css' => '01e6991e',
     'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
     'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
     'rsrc/css/phui/workboards/phui-workcard.css' => '8c536f90',
     'rsrc/css/phui/workboards/phui-workpanel.css' => 'bd546a49',
     'rsrc/css/sprite-login.css' => '18b368a6',
     'rsrc/css/sprite-tokens.css' => 'f1896dc5',
     'rsrc/css/syntax/syntax-default.css' => '055fc231',
     'rsrc/externals/d3/d3.min.js' => 'd67475f5',
     'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '23f8c698',
     'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '70983df0',
     'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => 'cd02f93b',
     'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => '351fd46a',
     'rsrc/externals/font/lato/lato-bold.eot' => '7367aa5e',
     'rsrc/externals/font/lato/lato-bold.svg' => '681aa4f5',
     'rsrc/externals/font/lato/lato-bold.ttf' => '66d3c296',
     'rsrc/externals/font/lato/lato-bold.woff' => '89d9fba7',
     'rsrc/externals/font/lato/lato-bold.woff2' => '389fcdb1',
     'rsrc/externals/font/lato/lato-bolditalic.eot' => '03eeb4da',
     'rsrc/externals/font/lato/lato-bolditalic.svg' => 'f56fa11c',
     'rsrc/externals/font/lato/lato-bolditalic.ttf' => '9c3aec21',
     'rsrc/externals/font/lato/lato-bolditalic.woff' => 'bfbd0616',
     'rsrc/externals/font/lato/lato-bolditalic.woff2' => 'bc7d1274',
     'rsrc/externals/font/lato/lato-italic.eot' => '7db5b247',
     'rsrc/externals/font/lato/lato-italic.svg' => 'b1ae496f',
     'rsrc/externals/font/lato/lato-italic.ttf' => '43eed813',
     'rsrc/externals/font/lato/lato-italic.woff' => 'c28975e1',
     'rsrc/externals/font/lato/lato-italic.woff2' => 'fffc0d8c',
     'rsrc/externals/font/lato/lato-regular.eot' => '06e0c291',
     'rsrc/externals/font/lato/lato-regular.svg' => '3ad95f53',
     'rsrc/externals/font/lato/lato-regular.ttf' => 'e2e9c398',
     'rsrc/externals/font/lato/lato-regular.woff' => '0b13d332',
     'rsrc/externals/font/lato/lato-regular.woff2' => '8f846797',
     'rsrc/externals/javelin/core/Event.js' => 'c03f2fb4',
     'rsrc/externals/javelin/core/Stratcom.js' => '0889b835',
     'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '048472d2',
     'rsrc/externals/javelin/core/__tests__/install.js' => '14a7e671',
     'rsrc/externals/javelin/core/__tests__/stratcom.js' => 'a28464bb',
     'rsrc/externals/javelin/core/__tests__/util.js' => 'e29a4354',
     'rsrc/externals/javelin/core/init.js' => '98e6504a',
     'rsrc/externals/javelin/core/init_node.js' => '16961339',
     'rsrc/externals/javelin/core/install.js' => '5902260c',
     'rsrc/externals/javelin/core/util.js' => '22ae1776',
     'rsrc/externals/javelin/docs/Base.js' => '5a401d7d',
     'rsrc/externals/javelin/docs/onload.js' => 'ee58fb62',
     'rsrc/externals/javelin/ext/fx/Color.js' => '78f811c9',
     'rsrc/externals/javelin/ext/fx/FX.js' => '34450586',
     'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => '202a2e85',
     'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '1c850a26',
     'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => '72960bc1',
     'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '225bbb98',
     'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => '6cfa0008',
     'rsrc/externals/javelin/ext/view/HTMLView.js' => 'f8c4e135',
     'rsrc/externals/javelin/ext/view/View.js' => '289bf236',
     'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => '876506b6',
     'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => 'a9942052',
     'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '9aae2b66',
     'rsrc/externals/javelin/ext/view/ViewVisitor.js' => '308f9fe4',
     'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => '6e50a13f',
     'rsrc/externals/javelin/ext/view/__tests__/View.js' => 'd284be5d',
     'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => 'a9f35511',
     'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '3a1b81f6',
     'rsrc/externals/javelin/lib/Cookie.js' => '05d290ef',
     'rsrc/externals/javelin/lib/DOM.js' => '94681e22',
     'rsrc/externals/javelin/lib/History.js' => '030b4f7a',
     'rsrc/externals/javelin/lib/JSON.js' => '541f81c3',
     'rsrc/externals/javelin/lib/Leader.js' => '0d2490ce',
     'rsrc/externals/javelin/lib/Mask.js' => '7c4d8998',
     'rsrc/externals/javelin/lib/Quicksand.js' => 'd3799cb4',
     'rsrc/externals/javelin/lib/Request.js' => '91863989',
     'rsrc/externals/javelin/lib/Resource.js' => '740956e1',
     'rsrc/externals/javelin/lib/Routable.js' => '6a18c42e',
     'rsrc/externals/javelin/lib/Router.js' => '32755edb',
     'rsrc/externals/javelin/lib/Scrollbar.js' => 'a43ae2ae',
     'rsrc/externals/javelin/lib/Sound.js' => 'e562708c',
     'rsrc/externals/javelin/lib/URI.js' => '2e255291',
     'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
     'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
     'rsrc/externals/javelin/lib/Workflow.js' => '958e9045',
     'rsrc/externals/javelin/lib/__tests__/Cookie.js' => 'ca686f71',
     'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249',
     'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae',
     'rsrc/externals/javelin/lib/__tests__/URI.js' => '6fff0c2b',
     'rsrc/externals/javelin/lib/__tests__/behavior.js' => '8426ebeb',
     'rsrc/externals/javelin/lib/behavior.js' => 'fce5d170',
     'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '89a1ae3a',
     'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'a4356cde',
     'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'a241536a',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '22ee68a5',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '23387297',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '5a79f6c3',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '8badee71',
     'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '80bff3af',
     'rsrc/favicons/favicon-16x16.png' => '4c51a03a',
     'rsrc/favicons/mask-icon.svg' => 'db699fe1',
     'rsrc/image/BFCFDA.png' => '74b5c88b',
     'rsrc/image/actions/edit.png' => 'fd987dff',
     'rsrc/image/avatar.png' => '0d17c6c4',
     'rsrc/image/checker_dark.png' => '7fc8fa7b',
     'rsrc/image/checker_light.png' => '3157a202',
     'rsrc/image/checker_lighter.png' => 'c45928c1',
+    'rsrc/image/chevron-in.png' => '1aa2f88f',
+    'rsrc/image/chevron-out.png' => 'c815e272',
     'rsrc/image/controls/checkbox-checked.png' => '1770d7a0',
     'rsrc/image/controls/checkbox-unchecked.png' => 'e1deba0a',
     'rsrc/image/d5d8e1.png' => '6764616e',
     'rsrc/image/darkload.gif' => '5bd41a89',
     'rsrc/image/divot.png' => '0fbe2453',
     'rsrc/image/examples/hero.png' => '5d8c4b21',
     'rsrc/image/grippy_texture.png' => 'a7d222b5',
     'rsrc/image/icon/fatcow/arrow_branch.png' => '98149d9f',
     'rsrc/image/icon/fatcow/arrow_merge.png' => 'e142f4f8',
     'rsrc/image/icon/fatcow/calendar_edit.png' => '5ff44a08',
     'rsrc/image/icon/fatcow/document_black.png' => 'd3515fa5',
     'rsrc/image/icon/fatcow/flag_blue.png' => '54db2e5c',
     'rsrc/image/icon/fatcow/flag_finish.png' => '2953a51b',
     'rsrc/image/icon/fatcow/flag_ghost.png' => '7d9ada92',
     'rsrc/image/icon/fatcow/flag_green.png' => '010f7161',
     'rsrc/image/icon/fatcow/flag_orange.png' => '6c384ca5',
     'rsrc/image/icon/fatcow/flag_pink.png' => '11ac6b12',
     'rsrc/image/icon/fatcow/flag_purple.png' => 'c4f423a4',
     'rsrc/image/icon/fatcow/flag_red.png' => '9e6d8817',
     'rsrc/image/icon/fatcow/flag_yellow.png' => '906733f4',
     'rsrc/image/icon/fatcow/key_question.png' => 'c10c26db',
     'rsrc/image/icon/fatcow/link.png' => '8edbf327',
     'rsrc/image/icon/fatcow/page_white_edit.png' => '17ef5625',
     'rsrc/image/icon/fatcow/page_white_put.png' => '82430c91',
     'rsrc/image/icon/fatcow/source/conduit.png' => '5b55130c',
     'rsrc/image/icon/fatcow/source/email.png' => '8a32b77f',
     'rsrc/image/icon/fatcow/source/fax.png' => '8bc2a49b',
     'rsrc/image/icon/fatcow/source/mobile.png' => '0a918412',
     'rsrc/image/icon/fatcow/source/tablet.png' => 'fc50b050',
     'rsrc/image/icon/fatcow/source/web.png' => '70433af3',
     'rsrc/image/icon/subscribe.png' => '07ef454e',
     'rsrc/image/icon/tango/attachment.png' => 'bac9032d',
     'rsrc/image/icon/tango/edit.png' => 'e6296206',
     'rsrc/image/icon/tango/go-down.png' => '0b903712',
     'rsrc/image/icon/tango/log.png' => '86b6a6f4',
     'rsrc/image/icon/tango/upload.png' => '3fe6b92d',
     'rsrc/image/icon/unsubscribe.png' => 'db04378a',
     'rsrc/image/lightblue-header.png' => 'e6d483c6',
     'rsrc/image/logo/light-eye.png' => '72337472',
     'rsrc/image/main_texture.png' => '894d03c4',
     'rsrc/image/menu_texture.png' => '896c9ade',
     'rsrc/image/people/harding.png' => '95b2db63',
     'rsrc/image/people/jefferson.png' => 'e883a3a2',
     'rsrc/image/people/lincoln.png' => 'be2c07c5',
     'rsrc/image/people/mckinley.png' => '6af510a0',
     'rsrc/image/people/taft.png' => 'b15ab07e',
     'rsrc/image/people/user0.png' => '4bc64b40',
     'rsrc/image/people/user1.png' => '8063f445',
     'rsrc/image/people/user2.png' => 'd28246c0',
     'rsrc/image/people/user3.png' => 'fb1ac12d',
     'rsrc/image/people/user4.png' => 'fe4fac8f',
     'rsrc/image/people/user5.png' => '3d07065c',
     'rsrc/image/people/user6.png' => 'e4bd47c8',
     'rsrc/image/people/user7.png' => '71d8fe8b',
     'rsrc/image/people/user8.png' => '85f86bf7',
     'rsrc/image/people/user9.png' => '523db8aa',
     'rsrc/image/people/washington.png' => '86159e68',
     'rsrc/image/phrequent_active.png' => 'de66dc50',
     'rsrc/image/phrequent_inactive.png' => '79c61baf',
     'rsrc/image/resize.png' => '9cc83373',
     'rsrc/image/sprite-login-X2.png' => '604545f6',
     'rsrc/image/sprite-login.png' => '7a001a9a',
     'rsrc/image/sprite-tokens-X2.png' => '21621dd9',
     'rsrc/image/sprite-tokens.png' => 'bede2580',
     'rsrc/image/texture/card-gradient.png' => 'e6892cb4',
     'rsrc/image/texture/dark-menu-hover.png' => '390a4fa1',
     'rsrc/image/texture/dark-menu.png' => '542f699c',
     'rsrc/image/texture/grip.png' => 'bc80753a',
     'rsrc/image/texture/panel-header-gradient.png' => '65004dbf',
     'rsrc/image/texture/phlnx-bg.png' => '6c9cd31d',
     'rsrc/image/texture/pholio-background.gif' => '84910bfc',
     'rsrc/image/texture/table_header.png' => '7652d1ad',
     'rsrc/image/texture/table_header_hover.png' => '12ea5236',
     'rsrc/image/texture/table_header_tall.png' => '5cc420c4',
     'rsrc/js/application/aphlict/Aphlict.js' => '022516b4',
     'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'e9a2940f',
     'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '4e61fa88',
     'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'c3703a16',
     'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => '070679fe',
     'rsrc/js/application/calendar/behavior-day-view.js' => '727a5a61',
     'rsrc/js/application/calendar/behavior-event-all-day.js' => '0b1bc990',
     'rsrc/js/application/calendar/behavior-month-view.js' => '158c64e0',
     'rsrc/js/application/config/behavior-reorder-fields.js' => '2539f834',
     'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'aec8e38c',
     'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '91befbcc',
     'rsrc/js/application/conpherence/behavior-durable-column.js' => 'fa6f30b2',
     'rsrc/js/application/conpherence/behavior-menu.js' => '8c2ed2bf',
     'rsrc/js/application/conpherence/behavior-participant-pane.js' => '43ba89a2',
     'rsrc/js/application/conpherence/behavior-pontificate.js' => '4ae58b5a',
     'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '5a6f6a06',
     'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '8f959ad0',
     'rsrc/js/application/countdown/timer.js' => '6a162524',
     'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => '3829a3cf',
     'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '09ecf50c',
     'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '076bd092',
     'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9',
     'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '9b1cbd76',
     'rsrc/js/application/diff/DiffChangeset.js' => 'e7cf10d6',
     'rsrc/js/application/diff/DiffChangesetList.js' => 'b91204e9',
     'rsrc/js/application/diff/DiffInline.js' => 'a4a14a94',
     'rsrc/js/application/diff/behavior-preview-link.js' => 'f51e9c17',
     'rsrc/js/application/differential/behavior-diff-radios.js' => '925fe8cd',
     'rsrc/js/application/differential/behavior-populate.js' => 'dfa1d313',
     'rsrc/js/application/differential/behavior-user-select.js' => 'e18685c0',
     'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '94243d89',
     'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831',
     'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572',
     'rsrc/js/application/diffusion/behavior-commit-graph.js' => '1c88f154',
     'rsrc/js/application/diffusion/behavior-locate-file.js' => '87428eb2',
     'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
     'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
     'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
     'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
     'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
     'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
     'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'b347a301',
     'rsrc/js/application/herald/HeraldRuleEditor.js' => '27daef73',
     'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
     'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
     'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'cffd39b4',
     'rsrc/js/application/maniphest/behavior-line-chart.js' => 'c8147a20',
     'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867',
     'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '8400307c',
     'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9',
     'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a',
     'rsrc/js/application/passphrase/passphrase-credential-control.js' => '48fe33d0',
     'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '3eed1f2b',
     'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => '5aa1544e',
     'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '02cb4398',
     'rsrc/js/application/phortune/behavior-test-payment-form.js' => '4a7fb02b',
     'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
     'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
     'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
     'rsrc/js/application/projects/WorkboardBoard.js' => '45d0b2b1',
     'rsrc/js/application/projects/WorkboardCard.js' => '9a513421',
     'rsrc/js/application/projects/WorkboardColumn.js' => '8573dc1b',
     'rsrc/js/application/projects/WorkboardController.js' => '42c7a5a7',
     'rsrc/js/application/projects/behavior-project-boards.js' => '05c74d65',
     'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
     'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
     'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
     'rsrc/js/application/releeph/releeph-request-state-change.js' => '9f081f05',
     'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'aa3a100c',
     'rsrc/js/application/repository/repository-crossreference.js' => 'db0c0214',
     'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e5bdb730',
     'rsrc/js/application/search/behavior-reorder-queries.js' => 'b86f297f',
     'rsrc/js/application/transactions/behavior-comment-actions.js' => '4dffaeb2',
     'rsrc/js/application/transactions/behavior-reorder-configs.js' => '4842f137',
     'rsrc/js/application/transactions/behavior-reorder-fields.js' => '0ad8d31f',
     'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '600f440c',
     'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '2bdadf1a',
     'rsrc/js/application/transactions/behavior-transaction-list.js' => '9cec214e',
     'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '70245195',
     'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '7b139193',
     'rsrc/js/application/uiexample/gesture-example.js' => '242dedd0',
     'rsrc/js/application/uiexample/notification-example.js' => '29819b75',
     'rsrc/js/core/Busy.js' => '5202e831',
     'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d',
     'rsrc/js/core/DraggableList.js' => '3c6bd549',
     'rsrc/js/core/Favicon.js' => '7930776a',
     'rsrc/js/core/FileUpload.js' => 'ab85e184',
     'rsrc/js/core/Hovercard.js' => '074f0783',
     'rsrc/js/core/KeyboardShortcut.js' => 'c9749dcd',
     'rsrc/js/core/KeyboardShortcutManager.js' => '37b8a04a',
     'rsrc/js/core/MultirowRowManager.js' => '5b54c823',
     'rsrc/js/core/Notification.js' => 'a9b91e3f',
     'rsrc/js/core/Prefab.js' => '5793d835',
     'rsrc/js/core/ShapedRequest.js' => 'abf88db8',
     'rsrc/js/core/TextAreaUtils.js' => 'f340a484',
     'rsrc/js/core/Title.js' => '43bc9360',
     'rsrc/js/core/ToolTip.js' => '83754533',
     'rsrc/js/core/behavior-active-nav.js' => '7353f43d',
     'rsrc/js/core/behavior-audio-source.js' => '3dc5ad43',
     'rsrc/js/core/behavior-autofocus.js' => '65bb0011',
     'rsrc/js/core/behavior-badge-view.js' => '92cdd7b6',
     'rsrc/js/core/behavior-bulk-editor.js' => 'aa6d2308',
     'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3',
     'rsrc/js/core/behavior-copy.js' => 'cf32921f',
     'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94',
     'rsrc/js/core/behavior-device.js' => '0cf79f45',
     'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '7ad020a5',
     'rsrc/js/core/behavior-fancy-datepicker.js' => '956f3eeb',
     'rsrc/js/core/behavior-file-tree.js' => 'ee82cedb',
     'rsrc/js/core/behavior-form.js' => '55d7b788',
     'rsrc/js/core/behavior-gesture.js' => 'b58d1a2a',
     'rsrc/js/core/behavior-global-drag-and-drop.js' => '1cab0e9a',
     'rsrc/js/core/behavior-high-security-warning.js' => 'dae2d55b',
     'rsrc/js/core/behavior-history-install.js' => '6a1583a8',
     'rsrc/js/core/behavior-hovercard.js' => '6c379000',
     'rsrc/js/core/behavior-keyboard-pager.js' => '1325b731',
     'rsrc/js/core/behavior-keyboard-shortcuts.js' => '2cc87f49',
     'rsrc/js/core/behavior-lightbox-attachments.js' => 'c7e748bf',
     'rsrc/js/core/behavior-line-linker.js' => 'e15c8b1f',
     'rsrc/js/core/behavior-linked-container.js' => '74446546',
     'rsrc/js/core/behavior-more.js' => '506aa3f4',
     'rsrc/js/core/behavior-object-selector.js' => 'a4af0b4a',
     'rsrc/js/core/behavior-oncopy.js' => '418f6684',
     'rsrc/js/core/behavior-phabricator-nav.js' => 'f166c949',
     'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '2f80333f',
     'rsrc/js/core/behavior-read-only-warning.js' => 'b9109f8f',
     'rsrc/js/core/behavior-redirect.js' => '407ee861',
     'rsrc/js/core/behavior-refresh-csrf.js' => '46116c01',
     'rsrc/js/core/behavior-remarkup-load-image.js' => '202bfa3f',
     'rsrc/js/core/behavior-remarkup-preview.js' => 'd8a86cfb',
     'rsrc/js/core/behavior-reorder-applications.js' => 'aa371860',
     'rsrc/js/core/behavior-reveal-content.js' => 'b105a3a6',
     'rsrc/js/core/behavior-scrollbar.js' => '92388bae',
     'rsrc/js/core/behavior-search-typeahead.js' => '1cb7d027',
     'rsrc/js/core/behavior-select-content.js' => 'e8240b50',
     'rsrc/js/core/behavior-select-on-click.js' => '66365ee2',
     'rsrc/js/core/behavior-setup-check-https.js' => '01384686',
     'rsrc/js/core/behavior-time-typeahead.js' => '5803b9e7',
     'rsrc/js/core/behavior-toggle-class.js' => 'f5c78ae3',
     'rsrc/js/core/behavior-tokenizer.js' => '3b4899b0',
     'rsrc/js/core/behavior-tooltip.js' => '73ecc1f8',
     'rsrc/js/core/behavior-user-menu.js' => '60cd9241',
     'rsrc/js/core/behavior-watch-anchor.js' => '0e6d261f',
     'rsrc/js/core/behavior-workflow.js' => '9623adc1',
     'rsrc/js/core/darkconsole/DarkLog.js' => '3b869402',
     'rsrc/js/core/darkconsole/DarkMessage.js' => '26cd4b73',
     'rsrc/js/core/darkconsole/behavior-dark-console.js' => 'f39d968b',
     'rsrc/js/core/phtize.js' => '2f1db1ed',
     'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '5cf0501a',
     'rsrc/js/phui/behavior-phui-file-upload.js' => 'e150bd50',
     'rsrc/js/phui/behavior-phui-selectable-list.js' => 'b26a41e4',
     'rsrc/js/phui/behavior-phui-submenu.js' => 'b5e9bff9',
     'rsrc/js/phui/behavior-phui-tab-group.js' => '242aa08b',
     'rsrc/js/phui/behavior-phui-timer-control.js' => 'f84bcbf4',
     'rsrc/js/phuix/PHUIXActionListView.js' => 'c68f183f',
     'rsrc/js/phuix/PHUIXActionView.js' => 'aaa08f3b',
     'rsrc/js/phuix/PHUIXAutocomplete.js' => '8f139ef0',
     'rsrc/js/phuix/PHUIXButtonView.js' => '55a24e84',
     'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bdce4d78',
     'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7',
     'rsrc/js/phuix/PHUIXFormControl.js' => '38c1f3fb',
     'rsrc/js/phuix/PHUIXIconView.js' => 'a5257c4e',
   ),
   'symbols' => array(
     'almanac-css' => '2e050f4f',
     'aphront-bars' => '4a327b4a',
     'aphront-dark-console-css' => '7f06cda2',
     'aphront-dialog-view-css' => 'b70c70df',
     'aphront-list-filter-view-css' => 'feb64255',
     'aphront-multi-column-view-css' => 'fbc00ba3',
     'aphront-panel-view-css' => '46923d46',
     'aphront-table-view-css' => '205053cd',
     'aphront-tokenizer-control-css' => 'b52d0668',
     'aphront-tooltip-css' => 'e3f2412f',
     'aphront-typeahead-control-css' => '8779483d',
     'application-search-view-css' => '0f7c06d8',
     'auth-css' => 'add92fd8',
     'bulk-job-css' => '73af99f5',
     'conduit-api-css' => 'ce2cfc41',
     'config-options-css' => '16c920ae',
     'conpherence-color-css' => 'b17746b0',
     'conpherence-durable-column-view' => '2d57072b',
     'conpherence-header-pane-css' => 'c9a3db8e',
     'conpherence-menu-css' => '67f4680d',
     'conpherence-message-pane-css' => 'd244db1e',
     'conpherence-notification-css' => '6a3d4e58',
     'conpherence-participant-pane-css' => '69e0058a',
     'conpherence-thread-manager' => 'aec8e38c',
     'conpherence-transaction-css' => '3a3f5e7e',
     'd3' => 'd67475f5',
-    'differential-changeset-view-css' => '73660575',
+    'differential-changeset-view-css' => '783a9206',
     'differential-core-view-css' => 'bdb93065',
     'differential-revision-add-comment-css' => '7e5900d9',
     'differential-revision-comment-css' => '7dbc8d1d',
     'differential-revision-history-css' => '8aa3eac5',
     'differential-revision-list-css' => '93d2df7d',
     'differential-table-of-contents-css' => '0e3364c7',
     'diffusion-css' => 'b54c77b0',
     'diffusion-icons-css' => '23b31a1b',
     'diffusion-readme-css' => 'b68a76e4',
     'diffusion-repository-css' => 'b89e8c6c',
     'diviner-shared-css' => '4bd263b0',
     'font-fontawesome' => '3883938a',
     'font-lato' => '23631304',
     'global-drag-and-drop-css' => '1d2713a4',
     'harbormaster-css' => '8dfe16b2',
     'herald-css' => '648d39e2',
     'herald-rule-editor' => '27daef73',
     'herald-test-css' => 'e004176f',
     'inline-comment-summary-css' => '81eb368d',
     'javelin-aphlict' => '022516b4',
     'javelin-behavior' => 'fce5d170',
     'javelin-behavior-aphlict-dropdown' => 'e9a2940f',
     'javelin-behavior-aphlict-listen' => '4e61fa88',
     'javelin-behavior-aphlict-status' => 'c3703a16',
     'javelin-behavior-aphront-basic-tokenizer' => '3b4899b0',
     'javelin-behavior-aphront-drag-and-drop-textarea' => '7ad020a5',
     'javelin-behavior-aphront-form-disable-on-submit' => '55d7b788',
     'javelin-behavior-aphront-more' => '506aa3f4',
     'javelin-behavior-audio-source' => '3dc5ad43',
     'javelin-behavior-audit-preview' => 'b7b73831',
     'javelin-behavior-badge-view' => '92cdd7b6',
     'javelin-behavior-bulk-editor' => 'aa6d2308',
     'javelin-behavior-bulk-job-reload' => '3829a3cf',
     'javelin-behavior-calendar-month-view' => '158c64e0',
     'javelin-behavior-choose-control' => '04f8a1e3',
     'javelin-behavior-comment-actions' => '4dffaeb2',
     'javelin-behavior-config-reorder-fields' => '2539f834',
     'javelin-behavior-conpherence-menu' => '8c2ed2bf',
     'javelin-behavior-conpherence-participant-pane' => '43ba89a2',
     'javelin-behavior-conpherence-pontificate' => '4ae58b5a',
     'javelin-behavior-conpherence-search' => '91befbcc',
     'javelin-behavior-countdown-timer' => '6a162524',
     'javelin-behavior-dark-console' => 'f39d968b',
     'javelin-behavior-dashboard-async-panel' => '09ecf50c',
     'javelin-behavior-dashboard-move-panels' => '076bd092',
     'javelin-behavior-dashboard-query-panel-select' => '1e413dc9',
     'javelin-behavior-dashboard-tab-panel' => '9b1cbd76',
     'javelin-behavior-day-view' => '727a5a61',
     'javelin-behavior-desktop-notifications-control' => '070679fe',
     'javelin-behavior-detect-timezone' => '78bc5d94',
     'javelin-behavior-device' => '0cf79f45',
     'javelin-behavior-diff-preview-link' => 'f51e9c17',
     'javelin-behavior-differential-diff-radios' => '925fe8cd',
     'javelin-behavior-differential-populate' => 'dfa1d313',
     'javelin-behavior-differential-user-select' => 'e18685c0',
     'javelin-behavior-diffusion-commit-branches' => '4b671572',
     'javelin-behavior-diffusion-commit-graph' => '1c88f154',
     'javelin-behavior-diffusion-locate-file' => '87428eb2',
     'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123',
     'javelin-behavior-document-engine' => '243d6c22',
     'javelin-behavior-doorkeeper-tag' => '6a85bc5a',
     'javelin-behavior-drydock-live-operation-status' => '47a0728b',
     'javelin-behavior-durable-column' => 'fa6f30b2',
     'javelin-behavior-editengine-reorder-configs' => '4842f137',
     'javelin-behavior-editengine-reorder-fields' => '0ad8d31f',
     'javelin-behavior-event-all-day' => '0b1bc990',
     'javelin-behavior-fancy-datepicker' => '956f3eeb',
     'javelin-behavior-global-drag-and-drop' => '1cab0e9a',
     'javelin-behavior-harbormaster-log' => 'b347a301',
     'javelin-behavior-herald-rule-editor' => '0922e81d',
     'javelin-behavior-high-security-warning' => 'dae2d55b',
     'javelin-behavior-history-install' => '6a1583a8',
     'javelin-behavior-icon-composer' => '38a6cedb',
     'javelin-behavior-launch-icon-composer' => 'a17b84f1',
     'javelin-behavior-lightbox-attachments' => 'c7e748bf',
     'javelin-behavior-line-chart' => 'c8147a20',
     'javelin-behavior-linked-container' => '74446546',
     'javelin-behavior-maniphest-batch-selector' => 'cffd39b4',
     'javelin-behavior-maniphest-list-editor' => 'c687e867',
     'javelin-behavior-maniphest-subpriority-editor' => '8400307c',
     'javelin-behavior-owners-path-editor' => 'ff688a7a',
     'javelin-behavior-passphrase-credential-control' => '48fe33d0',
     'javelin-behavior-phabricator-active-nav' => '7353f43d',
     'javelin-behavior-phabricator-autofocus' => '65bb0011',
     'javelin-behavior-phabricator-clipboard-copy' => 'cf32921f',
     'javelin-behavior-phabricator-file-tree' => 'ee82cedb',
     'javelin-behavior-phabricator-gesture' => 'b58d1a2a',
     'javelin-behavior-phabricator-gesture-example' => '242dedd0',
     'javelin-behavior-phabricator-keyboard-pager' => '1325b731',
     'javelin-behavior-phabricator-keyboard-shortcuts' => '2cc87f49',
     'javelin-behavior-phabricator-line-linker' => 'e15c8b1f',
     'javelin-behavior-phabricator-nav' => 'f166c949',
     'javelin-behavior-phabricator-notification-example' => '29819b75',
     'javelin-behavior-phabricator-object-selector' => 'a4af0b4a',
     'javelin-behavior-phabricator-oncopy' => '418f6684',
     'javelin-behavior-phabricator-remarkup-assist' => '2f80333f',
     'javelin-behavior-phabricator-reveal-content' => 'b105a3a6',
     'javelin-behavior-phabricator-search-typeahead' => '1cb7d027',
     'javelin-behavior-phabricator-show-older-transactions' => '600f440c',
     'javelin-behavior-phabricator-tooltips' => '73ecc1f8',
     'javelin-behavior-phabricator-transaction-comment-form' => '2bdadf1a',
     'javelin-behavior-phabricator-transaction-list' => '9cec214e',
     'javelin-behavior-phabricator-watch-anchor' => '0e6d261f',
     'javelin-behavior-pholio-mock-edit' => '3eed1f2b',
     'javelin-behavior-pholio-mock-view' => '5aa1544e',
     'javelin-behavior-phui-dropdown-menu' => '5cf0501a',
     'javelin-behavior-phui-file-upload' => 'e150bd50',
     'javelin-behavior-phui-hovercards' => '6c379000',
     'javelin-behavior-phui-selectable-list' => 'b26a41e4',
     'javelin-behavior-phui-submenu' => 'b5e9bff9',
     'javelin-behavior-phui-tab-group' => '242aa08b',
     'javelin-behavior-phui-timer-control' => 'f84bcbf4',
     'javelin-behavior-phuix-example' => 'c2c500a7',
     'javelin-behavior-policy-control' => '0eaa33a9',
     'javelin-behavior-policy-rule-editor' => '9347f172',
     'javelin-behavior-project-boards' => '05c74d65',
     'javelin-behavior-project-create' => '34c53422',
     'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
     'javelin-behavior-read-only-warning' => 'b9109f8f',
     'javelin-behavior-redirect' => '407ee861',
     'javelin-behavior-refresh-csrf' => '46116c01',
     'javelin-behavior-releeph-preview-branch' => '75184d68',
     'javelin-behavior-releeph-request-state-change' => '9f081f05',
     'javelin-behavior-releeph-request-typeahead' => 'aa3a100c',
     'javelin-behavior-remarkup-load-image' => '202bfa3f',
     'javelin-behavior-remarkup-preview' => 'd8a86cfb',
     'javelin-behavior-reorder-applications' => 'aa371860',
     'javelin-behavior-reorder-columns' => '8ac32fd9',
     'javelin-behavior-reorder-profile-menu-items' => 'e5bdb730',
     'javelin-behavior-repository-crossreference' => 'db0c0214',
     'javelin-behavior-scrollbar' => '92388bae',
     'javelin-behavior-search-reorder-queries' => 'b86f297f',
     'javelin-behavior-select-content' => 'e8240b50',
     'javelin-behavior-select-on-click' => '66365ee2',
     'javelin-behavior-setup-check-https' => '01384686',
     'javelin-behavior-stripe-payment-form' => '02cb4398',
     'javelin-behavior-test-payment-form' => '4a7fb02b',
     'javelin-behavior-time-typeahead' => '5803b9e7',
     'javelin-behavior-toggle-class' => 'f5c78ae3',
     'javelin-behavior-toggle-widget' => '8f959ad0',
     'javelin-behavior-typeahead-browse' => '70245195',
     'javelin-behavior-typeahead-search' => '7b139193',
     'javelin-behavior-user-menu' => '60cd9241',
     'javelin-behavior-view-placeholder' => 'a9942052',
     'javelin-behavior-workflow' => '9623adc1',
     'javelin-color' => '78f811c9',
     'javelin-cookie' => '05d290ef',
     'javelin-diffusion-locate-file-source' => '94243d89',
     'javelin-dom' => '94681e22',
     'javelin-dynval' => '202a2e85',
     'javelin-event' => 'c03f2fb4',
     'javelin-fx' => '34450586',
     'javelin-history' => '030b4f7a',
     'javelin-install' => '5902260c',
     'javelin-json' => '541f81c3',
     'javelin-leader' => '0d2490ce',
     'javelin-magical-init' => '98e6504a',
     'javelin-mask' => '7c4d8998',
     'javelin-quicksand' => 'd3799cb4',
     'javelin-reactor' => '1c850a26',
     'javelin-reactor-dom' => '6cfa0008',
     'javelin-reactor-node-calmer' => '225bbb98',
     'javelin-reactornode' => '72960bc1',
     'javelin-request' => '91863989',
     'javelin-resource' => '740956e1',
     'javelin-routable' => '6a18c42e',
     'javelin-router' => '32755edb',
     'javelin-scrollbar' => 'a43ae2ae',
     'javelin-sound' => 'e562708c',
     'javelin-stratcom' => '0889b835',
     'javelin-tokenizer' => '89a1ae3a',
     'javelin-typeahead' => 'a4356cde',
     'javelin-typeahead-composite-source' => '22ee68a5',
     'javelin-typeahead-normalizer' => 'a241536a',
     'javelin-typeahead-ondemand-source' => '23387297',
     'javelin-typeahead-preloaded-source' => '5a79f6c3',
     'javelin-typeahead-source' => '8badee71',
     'javelin-typeahead-static-source' => '80bff3af',
     'javelin-uri' => '2e255291',
     'javelin-util' => '22ae1776',
     'javelin-vector' => 'e9c80beb',
     'javelin-view' => '289bf236',
     'javelin-view-html' => 'f8c4e135',
     'javelin-view-interpreter' => '876506b6',
     'javelin-view-renderer' => '9aae2b66',
     'javelin-view-visitor' => '308f9fe4',
     'javelin-websocket' => 'fdc13e4e',
     'javelin-workboard-board' => '45d0b2b1',
     'javelin-workboard-card' => '9a513421',
     'javelin-workboard-column' => '8573dc1b',
     'javelin-workboard-controller' => '42c7a5a7',
     'javelin-workflow' => '958e9045',
     'maniphest-report-css' => '3d53188b',
     'maniphest-task-edit-css' => '272daa84',
     'maniphest-task-summary-css' => '61d1667e',
     'multirow-row-manager' => '5b54c823',
     'owners-path-editor' => '2a8b62d9',
     'owners-path-editor-css' => 'fa7c13ef',
     'paste-css' => 'b37bcd38',
     'path-typeahead' => 'ad486db3',
     'people-picture-menu-item-css' => 'fe8e07cf',
     'people-profile-css' => '2ea2daa1',
     'phabricator-action-list-view-css' => 'c4972757',
     'phabricator-busy' => '5202e831',
     'phabricator-chatlog-css' => 'abdc76ee',
     'phabricator-content-source-view-css' => 'cdf0d579',
     'phabricator-core-css' => '1b29ed61',
     'phabricator-countdown-css' => 'bff8012f',
     'phabricator-darklog' => '3b869402',
     'phabricator-darkmessage' => '26cd4b73',
     'phabricator-dashboard-css' => '4267d6c6',
     'phabricator-diff-changeset' => 'e7cf10d6',
     'phabricator-diff-changeset-list' => 'b91204e9',
     'phabricator-diff-inline' => 'a4a14a94',
     'phabricator-drag-and-drop-file-upload' => '4370900d',
     'phabricator-draggable-list' => '3c6bd549',
     'phabricator-fatal-config-template-css' => '20babf50',
     'phabricator-favicon' => '7930776a',
     'phabricator-feed-css' => 'd8b6e3f8',
     'phabricator-file-upload' => 'ab85e184',
     'phabricator-filetree-view-css' => '56cdd875',
     'phabricator-flag-css' => '2b77be8d',
     'phabricator-keyboard-shortcut' => 'c9749dcd',
     'phabricator-keyboard-shortcut-manager' => '37b8a04a',
     'phabricator-main-menu-view' => '8e2d9a28',
     'phabricator-nav-view-css' => 'f8a0c1bf',
     'phabricator-notification' => 'a9b91e3f',
     'phabricator-notification-css' => '30240bd2',
     'phabricator-notification-menu-css' => 'e6962e89',
     'phabricator-object-selector-css' => 'ee77366f',
     'phabricator-phtize' => '2f1db1ed',
     'phabricator-prefab' => '5793d835',
     'phabricator-remarkup-css' => '9e627d41',
     'phabricator-search-results-css' => '9ea70ace',
     'phabricator-shaped-request' => 'abf88db8',
     'phabricator-slowvote-css' => '1694baed',
     'phabricator-source-code-view-css' => '03d7ac28',
     'phabricator-standard-page-view' => '8a295cb9',
     'phabricator-textareautils' => 'f340a484',
     'phabricator-title' => '43bc9360',
     'phabricator-tooltip' => '83754533',
     'phabricator-ui-example-css' => 'b4795059',
     'phabricator-zindex-css' => '99c0f5eb',
     'phame-css' => '799febf9',
     'pholio-css' => '88ef5ef1',
     'pholio-edit-css' => '4df55b3b',
     'pholio-inline-comments-css' => '722b48c2',
     'phortune-credit-card-form' => 'd12d214f',
     'phortune-credit-card-form-css' => '3b9868a8',
     'phortune-css' => '12e8251a',
     'phortune-invoice-css' => '4436b241',
     'phrequent-css' => 'bd79cc67',
     'phriction-document-css' => '03380da0',
     'phui-action-panel-css' => '6c386cbf',
     'phui-badge-view-css' => '666e25ad',
     'phui-basic-nav-view-css' => '56ebd66d',
     'phui-big-info-view-css' => '362ad37b',
     'phui-box-css' => '5ed3b8cb',
     'phui-bulk-editor-css' => '374d5e30',
     'phui-button-bar-css' => 'a4aa75c4',
     'phui-button-css' => 'ea704902',
     'phui-button-simple-css' => '1ff278aa',
     'phui-calendar-css' => 'f11073aa',
     'phui-calendar-day-css' => '9597d706',
     'phui-calendar-list-css' => 'ccd7e4e2',
     'phui-calendar-month-css' => 'cb758c42',
     'phui-chart-css' => '7853a69b',
     'phui-cms-css' => '8c05c41e',
     'phui-comment-form-css' => '68a2d99a',
     'phui-comment-panel-css' => 'ec4e31c0',
     'phui-crumbs-view-css' => '614f43cf',
     'phui-curtain-view-css' => '68c5efb6',
     'phui-document-summary-view-css' => 'b068eed1',
     'phui-document-view-css' => '52b748a5',
     'phui-document-view-pro-css' => 'b9613a10',
     'phui-feed-story-css' => 'a0c05029',
     'phui-font-icon-base-css' => 'd7994e06',
     'phui-fontkit-css' => '9b714a5e',
     'phui-form-css' => '159e2d9c',
     'phui-form-view-css' => '01b796c0',
     'phui-head-thing-view-css' => 'd7f293df',
     'phui-header-view-css' => '285c9139',
     'phui-hovercard' => '074f0783',
     'phui-hovercard-view-css' => '6ca90fa0',
     'phui-icon-set-selector-css' => '7aa5f3ec',
     'phui-icon-view-css' => '4cbc684a',
     'phui-image-mask-css' => '62c7f4d2',
     'phui-info-view-css' => '37b8d9ce',
     'phui-inline-comment-view-css' => '48acce5b',
     'phui-invisible-character-view-css' => 'c694c4a4',
     'phui-left-right-css' => '68513c34',
     'phui-lightbox-css' => '4ebf22da',
     'phui-list-view-css' => '470b1adb',
     'phui-object-box-css' => 'f434b6be',
     'phui-oi-big-ui-css' => '9e037c7a',
     'phui-oi-color-css' => 'b517bfa0',
     'phui-oi-drag-ui-css' => 'da15d3dc',
     'phui-oi-flush-ui-css' => '490e2e2e',
     'phui-oi-list-view-css' => '909f3844',
     'phui-oi-simple-ui-css' => '6a30fa46',
     'phui-pager-css' => 'd022c7ad',
     'phui-pinboard-view-css' => '1f08f5d8',
     'phui-property-list-view-css' => 'cad62236',
     'phui-remarkup-preview-css' => '91767007',
     'phui-segment-bar-view-css' => '5166b370',
     'phui-spacing-css' => 'b05cadc3',
     'phui-status-list-view-css' => 'e5ff8be0',
     'phui-tag-view-css' => '29409667',
     'phui-theme-css' => '35883b37',
     'phui-timeline-view-css' => '1e348e4b',
     'phui-two-column-view-css' => '01e6991e',
     'phui-workboard-color-css' => 'e86de308',
     'phui-workboard-view-css' => '74fc9d98',
     'phui-workcard-view-css' => '8c536f90',
     'phui-workpanel-view-css' => 'bd546a49',
     'phuix-action-list-view' => 'c68f183f',
     'phuix-action-view' => 'aaa08f3b',
     'phuix-autocomplete' => '8f139ef0',
     'phuix-button-view' => '55a24e84',
     'phuix-dropdown-menu' => 'bdce4d78',
     'phuix-form-control-view' => '38c1f3fb',
     'phuix-icon-view' => 'a5257c4e',
     'policy-css' => 'ceb56a08',
     'policy-edit-css' => '8794e2ed',
     'policy-transaction-detail-css' => 'c02b8384',
     'ponder-view-css' => '05a09d0a',
     'project-card-view-css' => '3b1f7b20',
     'project-view-css' => '567858b3',
     'releeph-core' => 'f81ff2db',
     'releeph-preview-branch' => '22db5c07',
     'releeph-request-differential-create-dialog' => '0ac1ea31',
     'releeph-request-typeahead-css' => 'bce37359',
     'setup-issue-css' => '5eed85b2',
     'sprite-login-css' => '18b368a6',
     'sprite-tokens-css' => 'f1896dc5',
     'syntax-default-css' => '055fc231',
     'syntax-highlighting-css' => '8a16f91b',
     'tokens-css' => 'ce5a50bd',
     'typeahead-browse-css' => 'b7ed02d2',
     'unhandled-exception-css' => '9ecfc00d',
   ),
   'requires' => array(
     '01384686' => array(
       'javelin-behavior',
       'javelin-uri',
       'phabricator-notification',
     ),
     '022516b4' => array(
       'javelin-install',
       'javelin-util',
       'javelin-websocket',
       'javelin-leader',
       'javelin-json',
     ),
     '02cb4398' => array(
       'javelin-behavior',
       'javelin-dom',
       'phortune-credit-card-form',
     ),
     '030b4f7a' => array(
       'javelin-stratcom',
       'javelin-install',
       'javelin-uri',
       'javelin-util',
     ),
     '04f8a1e3' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-workflow',
     ),
     '05c74d65' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-vector',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-workboard-controller',
     ),
     '05d290ef' => array(
       'javelin-install',
       'javelin-util',
     ),
     '070679fe' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-uri',
       'phabricator-notification',
     ),
     '074f0783' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-vector',
       'javelin-request',
       'javelin-uri',
     ),
     '076bd092' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-workflow',
       'phabricator-draggable-list',
     ),
     '0889b835' => array(
       'javelin-install',
       'javelin-event',
       'javelin-util',
       'javelin-magical-init',
     ),
     '0922e81d' => array(
       'herald-rule-editor',
       'javelin-behavior',
     ),
     '09ecf50c' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-workflow',
     ),
     '0ad8d31f' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     '0cf79f45' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-vector',
       'javelin-install',
     ),
     '0d2490ce' => array(
       'javelin-install',
     ),
     '0e6d261f' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-vector',
     ),
     '0eaa33a9' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'phuix-dropdown-menu',
       'phuix-action-list-view',
       'phuix-action-view',
       'javelin-workflow',
       'phuix-icon-view',
     ),
     '1325b731' => array(
       'javelin-behavior',
       'javelin-uri',
       'phabricator-keyboard-shortcut',
     ),
     '1c850a26' => array(
       'javelin-install',
       'javelin-util',
     ),
     '1c88f154' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     '1cab0e9a' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-uri',
       'javelin-mask',
       'phabricator-drag-and-drop-file-upload',
     ),
     '1cb7d027' => array(
       'javelin-behavior',
       'javelin-typeahead-ondemand-source',
       'javelin-typeahead',
       'javelin-dom',
       'javelin-uri',
       'javelin-util',
       'javelin-stratcom',
       'phabricator-prefab',
       'phuix-icon-view',
     ),
     '1e413dc9' => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     '1ff278aa' => array(
       'phui-button-css',
     ),
     '202a2e85' => array(
       'javelin-install',
       'javelin-reactornode',
       'javelin-util',
       'javelin-reactor',
     ),
     '202bfa3f' => array(
       'javelin-behavior',
       'javelin-request',
     ),
     '225bbb98' => array(
       'javelin-install',
       'javelin-reactor',
       'javelin-util',
     ),
     '22ee68a5' => array(
       'javelin-install',
       'javelin-typeahead-source',
       'javelin-util',
     ),
     23387297 => array(
       'javelin-install',
       'javelin-util',
       'javelin-request',
       'javelin-typeahead-source',
     ),
     23631304 => array(
       'phui-fontkit-css',
     ),
     '242aa08b' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '242dedd0' => array(
       'javelin-stratcom',
       'javelin-behavior',
       'javelin-vector',
       'javelin-dom',
     ),
     '243d6c22' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     '2539f834' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-json',
       'phabricator-draggable-list',
     ),
     '27daef73' => array(
       'multirow-row-manager',
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-json',
       'phabricator-prefab',
     ),
     '289bf236' => array(
       'javelin-install',
       'javelin-util',
     ),
     '29819b75' => array(
       'phabricator-notification',
       'javelin-stratcom',
       'javelin-behavior',
     ),
     '2a8b62d9' => array(
       'multirow-row-manager',
       'javelin-install',
       'path-typeahead',
       'javelin-dom',
       'javelin-util',
       'phabricator-prefab',
       'phuix-form-control-view',
     ),
     '2bdadf1a' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-request',
       'phabricator-shaped-request',
     ),
     '2cc87f49' => array(
       'javelin-behavior',
       'javelin-workflow',
       'javelin-json',
       'javelin-dom',
       'phabricator-keyboard-shortcut',
     ),
     '2e255291' => array(
       'javelin-install',
       'javelin-util',
       'javelin-stratcom',
     ),
     '2f1db1ed' => array(
       'javelin-util',
     ),
     '2f80333f' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'phabricator-phtize',
       'phabricator-textareautils',
       'javelin-workflow',
       'javelin-vector',
       'phuix-autocomplete',
       'javelin-mask',
     ),
     '308f9fe4' => array(
       'javelin-install',
       'javelin-util',
     ),
     '32755edb' => array(
       'javelin-install',
       'javelin-util',
     ),
     34450586 => array(
       'javelin-color',
       'javelin-install',
       'javelin-util',
     ),
     '34c53422' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
     ),
     '37b8a04a' => array(
       'javelin-install',
       'javelin-util',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-vector',
     ),
     '3829a3cf' => array(
       'javelin-behavior',
       'javelin-uri',
     ),
     '38a6cedb' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     '38c1f3fb' => array(
       'javelin-install',
       'javelin-dom',
     ),
     '3b4899b0' => array(
       'javelin-behavior',
       'phabricator-prefab',
     ),
     '3c6bd549' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-util',
       'javelin-vector',
       'javelin-magical-init',
     ),
     '3dc5ad43' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-dom',
     ),
     '3eed1f2b' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-workflow',
       'javelin-quicksand',
       'phabricator-phtize',
       'phabricator-drag-and-drop-file-upload',
       'phabricator-draggable-list',
     ),
     '407ee861' => array(
       'javelin-behavior',
       'javelin-uri',
     ),
     '418f6684' => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     '42c7a5a7' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-util',
       'javelin-vector',
       'javelin-stratcom',
       'javelin-workflow',
       'phabricator-drag-and-drop-file-upload',
       'javelin-workboard-board',
     ),
     '4370900d' => array(
       'javelin-install',
       'javelin-util',
       'javelin-request',
       'javelin-dom',
       'javelin-uri',
       'phabricator-file-upload',
     ),
     '43ba89a2' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-util',
       'phabricator-notification',
       'conpherence-thread-manager',
     ),
     '43bc9360' => array(
       'javelin-install',
     ),
     '45d0b2b1' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-workflow',
       'phabricator-draggable-list',
       'javelin-workboard-column',
     ),
     '46116c01' => array(
       'javelin-request',
       'javelin-behavior',
       'javelin-dom',
       'javelin-router',
       'javelin-util',
       'phabricator-busy',
     ),
     '47a0728b' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-request',
     ),
     '4842f137' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     '48fe33d0' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-util',
       'javelin-uri',
     ),
     '490e2e2e' => array(
       'phui-oi-list-view-css',
     ),
     '4a7fb02b' => array(
       'javelin-behavior',
       'javelin-dom',
       'phortune-credit-card-form',
     ),
     '4ae58b5a' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-workflow',
       'javelin-stratcom',
       'conpherence-thread-manager',
     ),
     '4b671572' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-request',
     ),
     '4dffaeb2' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phuix-form-control-view',
       'phuix-icon-view',
       'javelin-behavior-phabricator-gesture',
     ),
     '4e61fa88' => array(
       'javelin-behavior',
       'javelin-aphlict',
       'javelin-stratcom',
       'javelin-request',
       'javelin-uri',
       'javelin-dom',
       'javelin-json',
       'javelin-router',
       'javelin-util',
       'javelin-leader',
       'javelin-sound',
       'phabricator-notification',
     ),
     '506aa3f4' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '5202e831' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-fx',
     ),
     '541f81c3' => array(
       'javelin-install',
     ),
     '55a24e84' => array(
       'javelin-install',
       'javelin-dom',
     ),
     '55d7b788' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '5793d835' => array(
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-typeahead',
       'javelin-tokenizer',
       'javelin-typeahead-preloaded-source',
       'javelin-typeahead-ondemand-source',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-util',
     ),
     '5803b9e7' => array(
       'javelin-behavior',
       'javelin-util',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-typeahead-static-source',
     ),
     '5902260c' => array(
       'javelin-util',
       'javelin-magical-init',
     ),
     '5a6f6a06' => array(
       'javelin-behavior',
       'javelin-quicksand',
     ),
     '5a79f6c3' => array(
       'javelin-install',
       'javelin-util',
       'javelin-request',
       'javelin-typeahead-source',
     ),
     '5aa1544e' => array(
       'javelin-behavior',
       'javelin-util',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-vector',
       'javelin-magical-init',
       'javelin-request',
       'javelin-history',
       'javelin-workflow',
       'javelin-mask',
       'javelin-behavior-device',
       'phabricator-keyboard-shortcut',
     ),
     '5b54c823' => array(
       'javelin-install',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-util',
     ),
     '5cf0501a' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'phuix-dropdown-menu',
     ),
     '600f440c' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'phabricator-busy',
     ),
     '60cd9241' => array(
       'javelin-behavior',
     ),
     '65bb0011' => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     '66365ee2' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '6a1583a8' => array(
       'javelin-behavior',
       'javelin-history',
     ),
     '6a162524' => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     '6a18c42e' => array(
       'javelin-install',
     ),
     '6a30fa46' => array(
       'phui-oi-list-view-css',
     ),
     '6a85bc5a' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-json',
       'javelin-workflow',
       'javelin-magical-init',
     ),
     '6c379000' => array(
       'javelin-behavior',
       'javelin-behavior-device',
       'javelin-stratcom',
       'javelin-vector',
       'phui-hovercard',
     ),
     '6cfa0008' => array(
       'javelin-dom',
       'javelin-dynval',
       'javelin-reactor',
       'javelin-reactornode',
       'javelin-install',
       'javelin-util',
     ),
     70245195 => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
     ),
     '727a5a61' => array(
       'phuix-icon-view',
     ),
     '72960bc1' => array(
       'javelin-install',
       'javelin-reactor',
       'javelin-util',
       'javelin-reactor-node-calmer',
     ),
     '7353f43d' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-dom',
       'javelin-uri',
     ),
-    73660575 => array(
-      'phui-inline-comment-view-css',
-    ),
     '73ecc1f8' => array(
       'javelin-behavior',
       'javelin-behavior-device',
       'javelin-stratcom',
       'phabricator-tooltip',
     ),
     '740956e1' => array(
       'javelin-util',
       'javelin-uri',
       'javelin-install',
     ),
     74446546 => array(
       'javelin-behavior',
       'javelin-dom',
     ),
     '75184d68' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-uri',
       'javelin-request',
     ),
+    '783a9206' => array(
+      'phui-inline-comment-view-css',
+    ),
     '78bc5d94' => array(
       'javelin-behavior',
       'javelin-uri',
       'phabricator-notification',
     ),
     '78f811c9' => array(
       'javelin-install',
     ),
     '7930776a' => array(
       'javelin-install',
       'javelin-dom',
     ),
     '7ad020a5' => array(
       'javelin-behavior',
       'javelin-dom',
       'phabricator-drag-and-drop-file-upload',
       'phabricator-textareautils',
     ),
     '7b139193' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
     ),
     '7c4d8998' => array(
       'javelin-install',
       'javelin-dom',
     ),
     '80bff3af' => array(
       'javelin-install',
       'javelin-typeahead-source',
     ),
     83754533 => array(
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-vector',
     ),
     '8400307c' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'phabricator-draggable-list',
     ),
     '8573dc1b' => array(
       'javelin-install',
       'javelin-workboard-card',
     ),
     '87428eb2' => array(
       'javelin-behavior',
       'javelin-diffusion-locate-file-source',
       'javelin-dom',
       'javelin-typeahead',
       'javelin-uri',
     ),
     '876506b6' => array(
       'javelin-view',
       'javelin-install',
       'javelin-dom',
     ),
     '89a1ae3a' => array(
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-install',
     ),
     '8a16f91b' => array(
       'syntax-default-css',
     ),
     '8ac32fd9' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     '8badee71' => array(
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-typeahead-normalizer',
     ),
     '8c2ed2bf' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-behavior-device',
       'javelin-history',
       'javelin-vector',
       'javelin-scrollbar',
       'phabricator-title',
       'phabricator-shaped-request',
       'conpherence-thread-manager',
     ),
     '8e2d9a28' => array(
       'phui-theme-css',
     ),
     '8f139ef0' => array(
       'javelin-install',
       'javelin-dom',
       'phuix-icon-view',
       'phabricator-prefab',
     ),
     '8f959ad0' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-workflow',
       'javelin-stratcom',
     ),
     91863989 => array(
       'javelin-install',
       'javelin-stratcom',
       'javelin-util',
       'javelin-behavior',
       'javelin-json',
       'javelin-dom',
       'javelin-resource',
       'javelin-routable',
     ),
     '91befbcc' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-workflow',
       'javelin-stratcom',
     ),
     '92388bae' => array(
       'javelin-behavior',
       'javelin-scrollbar',
     ),
     '925fe8cd' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '92cdd7b6' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     '9347f172' => array(
       'javelin-behavior',
       'multirow-row-manager',
       'javelin-dom',
       'javelin-util',
       'phabricator-prefab',
       'javelin-json',
     ),
     '94243d89' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-typeahead-preloaded-source',
       'javelin-util',
     ),
     '94681e22' => array(
       'javelin-magical-init',
       'javelin-install',
       'javelin-util',
       'javelin-vector',
       'javelin-stratcom',
     ),
     '956f3eeb' => array(
       'javelin-behavior',
       'javelin-util',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-vector',
     ),
     '958e9045' => array(
       'javelin-stratcom',
       'javelin-request',
       'javelin-dom',
       'javelin-vector',
       'javelin-install',
       'javelin-util',
       'javelin-mask',
       'javelin-uri',
       'javelin-routable',
     ),
     '9623adc1' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'javelin-router',
     ),
     '9a513421' => array(
       'javelin-install',
     ),
     '9aae2b66' => array(
       'javelin-install',
       'javelin-util',
     ),
     '9b1cbd76' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     '9cec214e' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'javelin-uri',
       'phabricator-textareautils',
     ),
     '9e037c7a' => array(
       'phui-oi-list-view-css',
     ),
     '9f081f05' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-util',
       'phabricator-keyboard-shortcut',
     ),
     'a17b84f1' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-workflow',
     ),
     'a241536a' => array(
       'javelin-install',
     ),
     'a4356cde' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-vector',
       'javelin-util',
     ),
     'a43ae2ae' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-vector',
     ),
     'a4a14a94' => array(
       'javelin-dom',
     ),
     'a4aa75c4' => array(
       'phui-button-css',
       'phui-button-simple-css',
     ),
     'a4af0b4a' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-request',
       'javelin-util',
     ),
     'a5257c4e' => array(
       'javelin-install',
       'javelin-dom',
     ),
     'a9942052' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-view-renderer',
       'javelin-install',
     ),
     'a9b91e3f' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-util',
       'phabricator-notification-css',
     ),
     'aa371860' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     'aa3a100c' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-typeahead',
       'javelin-typeahead-ondemand-source',
       'javelin-dom',
     ),
     'aa6d2308' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'multirow-row-manager',
       'javelin-json',
       'phuix-form-control-view',
     ),
     'aaa08f3b' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-util',
     ),
     'ab85e184' => array(
       'javelin-install',
       'javelin-dom',
       'phabricator-notification',
     ),
     'abf88db8' => array(
       'javelin-install',
       'javelin-util',
       'javelin-request',
       'javelin-router',
     ),
     'ad486db3' => array(
       'javelin-install',
       'javelin-typeahead',
       'javelin-dom',
       'javelin-request',
       'javelin-typeahead-ondemand-source',
       'javelin-util',
     ),
     'aec8e38c' => array(
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-install',
       'javelin-aphlict',
       'javelin-workflow',
       'javelin-router',
       'javelin-behavior-device',
       'javelin-vector',
     ),
     'b105a3a6' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'b26a41e4' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'b347a301' => array(
       'javelin-behavior',
     ),
     'b517bfa0' => array(
       'phui-oi-list-view-css',
     ),
     'b52d0668' => array(
       'aphront-typeahead-control-css',
       'phui-tag-view-css',
     ),
     'b58d1a2a' => array(
       'javelin-behavior',
       'javelin-behavior-device',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-dom',
       'javelin-magical-init',
     ),
     'b5e9bff9' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'b7b73831' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'phabricator-shaped-request',
     ),
     'b86f297f' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     'b9109f8f' => array(
       'javelin-behavior',
       'javelin-uri',
       'phabricator-notification',
     ),
     'b91204e9' => array(
       'javelin-install',
       'phuix-button-view',
     ),
     'bd546a49' => array(
       'phui-workcard-view-css',
     ),
     'bdce4d78' => array(
       'javelin-install',
       'javelin-util',
       'javelin-dom',
       'javelin-vector',
       'javelin-stratcom',
     ),
     'c03f2fb4' => array(
       'javelin-install',
     ),
     'c2c500a7' => array(
       'javelin-install',
       'javelin-dom',
       'phuix-button-view',
     ),
     'c3703a16' => array(
       'javelin-behavior',
       'javelin-aphlict',
       'phabricator-phtize',
       'javelin-dom',
     ),
     'c687e867' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-fx',
       'javelin-util',
     ),
     'c68f183f' => array(
       'javelin-install',
       'javelin-dom',
     ),
     'c715c123' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'javelin-workflow',
       'javelin-json',
     ),
     'c7e748bf' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-mask',
       'javelin-util',
       'phuix-icon-view',
       'phabricator-busy',
     ),
     'c8147a20' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-vector',
       'phui-chart-css',
     ),
     'c9749dcd' => array(
       'javelin-install',
       'javelin-util',
       'phabricator-keyboard-shortcut-manager',
     ),
     'cf32921f' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     'cffd39b4' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-util',
     ),
     'd12d214f' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-json',
       'javelin-workflow',
       'javelin-util',
     ),
     'd3799cb4' => array(
       'javelin-install',
     ),
     'd8a86cfb' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-util',
       'phabricator-shaped-request',
     ),
     'da15d3dc' => array(
       'phui-oi-list-view-css',
     ),
     'dae2d55b' => array(
       'javelin-behavior',
       'javelin-uri',
       'phabricator-notification',
     ),
     'db0c0214' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-uri',
     ),
     'dfa1d313' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'phabricator-tooltip',
       'phabricator-diff-changeset-list',
       'phabricator-diff-changeset',
     ),
     'e150bd50' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'phuix-dropdown-menu',
     ),
     'e15c8b1f' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-history',
     ),
     'e18685c0' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
     ),
     'e562708c' => array(
       'javelin-install',
     ),
     'e5bdb730' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-workflow',
       'javelin-dom',
       'phabricator-draggable-list',
     ),
     'e7cf10d6' => array(
       'javelin-dom',
       'javelin-util',
       'javelin-stratcom',
       'javelin-install',
       'javelin-workflow',
       'javelin-router',
       'javelin-behavior-device',
       'javelin-vector',
       'phabricator-diff-inline',
     ),
     'e8240b50' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'e9a2940f' => array(
       'javelin-behavior',
       'javelin-request',
       'javelin-stratcom',
       'javelin-vector',
       'javelin-dom',
       'javelin-uri',
       'javelin-behavior-device',
       'phabricator-title',
       'phabricator-favicon',
     ),
     'e9c80beb' => array(
       'javelin-install',
       'javelin-event',
     ),
     'ec4e31c0' => array(
       'phui-timeline-view-css',
     ),
     'ee77366f' => array(
       'aphront-dialog-view-css',
     ),
     'ee82cedb' => array(
       'javelin-behavior',
       'phabricator-keyboard-shortcut',
       'javelin-stratcom',
     ),
     'f166c949' => array(
       'javelin-behavior',
       'javelin-behavior-device',
       'javelin-stratcom',
       'javelin-dom',
       'javelin-magical-init',
       'javelin-vector',
       'javelin-request',
       'javelin-util',
     ),
     'f340a484' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-vector',
     ),
     'f39d968b' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-util',
       'javelin-dom',
       'javelin-request',
       'phabricator-keyboard-shortcut',
       'phabricator-darklog',
       'phabricator-darkmessage',
     ),
     'f51e9c17' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'f5c78ae3' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'f84bcbf4' => array(
       'javelin-behavior',
       'javelin-stratcom',
       'javelin-dom',
     ),
     'f8c4e135' => array(
       'javelin-install',
       'javelin-dom',
       'javelin-view-visitor',
       'javelin-util',
     ),
     'fa6f30b2' => array(
       'javelin-behavior',
       'javelin-dom',
       'javelin-stratcom',
       'javelin-behavior-device',
       'javelin-scrollbar',
       'javelin-quicksand',
       'phabricator-keyboard-shortcut',
       'conpherence-thread-manager',
     ),
     'fce5d170' => array(
       'javelin-magical-init',
       'javelin-util',
     ),
     'fdc13e4e' => array(
       'javelin-install',
     ),
     'ff688a7a' => array(
       'owners-path-editor',
       'javelin-behavior',
     ),
   ),
   'packages' => array(
     'conpherence.pkg.css' => array(
       'conpherence-durable-column-view',
       'conpherence-menu-css',
       'conpherence-color-css',
       'conpherence-message-pane-css',
       'conpherence-notification-css',
       'conpherence-transaction-css',
       'conpherence-participant-pane-css',
       'conpherence-header-pane-css',
     ),
     'conpherence.pkg.js' => array(
       'javelin-behavior-conpherence-menu',
       'javelin-behavior-conpherence-participant-pane',
       'javelin-behavior-conpherence-pontificate',
       'javelin-behavior-toggle-widget',
     ),
     'core.pkg.css' => array(
       'phabricator-core-css',
       'phabricator-zindex-css',
       'phui-button-css',
       'phui-button-simple-css',
       'phui-theme-css',
       'phabricator-standard-page-view',
       'aphront-dialog-view-css',
       'phui-form-view-css',
       'aphront-panel-view-css',
       'aphront-table-view-css',
       'aphront-tokenizer-control-css',
       'aphront-typeahead-control-css',
       'aphront-list-filter-view-css',
       'application-search-view-css',
       'phabricator-remarkup-css',
       'syntax-highlighting-css',
       'syntax-default-css',
       'phui-pager-css',
       'aphront-tooltip-css',
       'phabricator-flag-css',
       'phui-info-view-css',
       'phabricator-main-menu-view',
       'phabricator-notification-css',
       'phabricator-notification-menu-css',
       'phui-lightbox-css',
       'phui-comment-panel-css',
       'phui-header-view-css',
       'phabricator-nav-view-css',
       'phui-basic-nav-view-css',
       'phui-crumbs-view-css',
       'phui-oi-list-view-css',
       'phui-oi-color-css',
       'phui-oi-big-ui-css',
       'phui-oi-drag-ui-css',
       'phui-oi-simple-ui-css',
       'phui-oi-flush-ui-css',
       'global-drag-and-drop-css',
       'phui-spacing-css',
       'phui-form-css',
       'phui-icon-view-css',
       'phabricator-action-list-view-css',
       'phui-property-list-view-css',
       'phui-tag-view-css',
       'phui-list-view-css',
       'font-fontawesome',
       'font-lato',
       'phui-font-icon-base-css',
       'phui-fontkit-css',
       'phui-box-css',
       'phui-object-box-css',
       'phui-timeline-view-css',
       'phui-two-column-view-css',
       'phui-curtain-view-css',
       'sprite-login-css',
       'sprite-tokens-css',
       'tokens-css',
       'auth-css',
       'phui-status-list-view-css',
       'phui-feed-story-css',
       'phabricator-feed-css',
       'phabricator-dashboard-css',
       'aphront-multi-column-view-css',
     ),
     'core.pkg.js' => array(
       'javelin-util',
       'javelin-install',
       'javelin-event',
       'javelin-stratcom',
       'javelin-behavior',
       'javelin-resource',
       'javelin-request',
       'javelin-vector',
       'javelin-dom',
       'javelin-json',
       'javelin-uri',
       'javelin-workflow',
       'javelin-mask',
       'javelin-typeahead',
       'javelin-typeahead-normalizer',
       'javelin-typeahead-source',
       'javelin-typeahead-preloaded-source',
       'javelin-typeahead-ondemand-source',
       'javelin-tokenizer',
       'javelin-history',
       'javelin-router',
       'javelin-routable',
       'javelin-behavior-aphront-basic-tokenizer',
       'javelin-behavior-workflow',
       'javelin-behavior-aphront-form-disable-on-submit',
       'phabricator-keyboard-shortcut-manager',
       'phabricator-keyboard-shortcut',
       'javelin-behavior-phabricator-keyboard-shortcuts',
       'javelin-behavior-refresh-csrf',
       'javelin-behavior-phabricator-watch-anchor',
       'javelin-behavior-phabricator-autofocus',
       'phuix-dropdown-menu',
       'phuix-action-list-view',
       'phuix-action-view',
       'phuix-icon-view',
       'phabricator-phtize',
       'javelin-behavior-phabricator-oncopy',
       'phabricator-tooltip',
       'javelin-behavior-phabricator-tooltips',
       'phabricator-prefab',
       'javelin-behavior-device',
       'javelin-behavior-toggle-class',
       'javelin-behavior-lightbox-attachments',
       'phabricator-busy',
       'javelin-sound',
       'javelin-aphlict',
       'phabricator-notification',
       'javelin-behavior-aphlict-listen',
       'javelin-behavior-phabricator-search-typeahead',
       'javelin-behavior-aphlict-dropdown',
       'javelin-behavior-history-install',
       'javelin-behavior-phabricator-gesture',
       'javelin-behavior-phabricator-active-nav',
       'javelin-behavior-phabricator-nav',
       'javelin-behavior-phabricator-remarkup-assist',
       'phabricator-textareautils',
       'phabricator-file-upload',
       'javelin-behavior-global-drag-and-drop',
       'javelin-behavior-phabricator-reveal-content',
       'phui-hovercard',
       'javelin-behavior-phui-hovercards',
       'javelin-color',
       'javelin-fx',
       'phabricator-draggable-list',
       'javelin-behavior-phabricator-transaction-list',
       'javelin-behavior-phabricator-show-older-transactions',
       'javelin-behavior-phui-dropdown-menu',
       'javelin-behavior-doorkeeper-tag',
       'phabricator-title',
       'javelin-leader',
       'javelin-websocket',
       'javelin-behavior-dashboard-async-panel',
       'javelin-behavior-dashboard-tab-panel',
       'javelin-quicksand',
       'javelin-behavior-quicksand-blacklist',
       'javelin-behavior-high-security-warning',
       'javelin-behavior-read-only-warning',
       'javelin-scrollbar',
       'javelin-behavior-scrollbar',
       'javelin-behavior-durable-column',
       'conpherence-thread-manager',
       'javelin-behavior-detect-timezone',
       'javelin-behavior-setup-check-https',
       'javelin-behavior-aphlict-status',
       'javelin-behavior-user-menu',
       'phabricator-favicon',
     ),
     'differential.pkg.css' => array(
       'differential-core-view-css',
       'differential-changeset-view-css',
       'differential-revision-history-css',
       'differential-revision-list-css',
       'differential-table-of-contents-css',
       'differential-revision-comment-css',
       'differential-revision-add-comment-css',
       'phabricator-object-selector-css',
       'phabricator-content-source-view-css',
       'inline-comment-summary-css',
       'phui-inline-comment-view-css',
       'phabricator-filetree-view-css',
     ),
     'differential.pkg.js' => array(
       'phabricator-drag-and-drop-file-upload',
       'phabricator-shaped-request',
       'javelin-behavior-differential-populate',
       'javelin-behavior-differential-diff-radios',
       'javelin-behavior-aphront-drag-and-drop-textarea',
       'javelin-behavior-phabricator-object-selector',
       'javelin-behavior-repository-crossreference',
       'javelin-behavior-differential-user-select',
       'javelin-behavior-aphront-more',
       'phabricator-diff-inline',
       'phabricator-diff-changeset',
       'phabricator-diff-changeset-list',
     ),
     'diffusion.pkg.css' => array(
       'diffusion-icons-css',
     ),
     'diffusion.pkg.js' => array(
       'javelin-behavior-diffusion-pull-lastmodified',
       'javelin-behavior-diffusion-commit-graph',
       'javelin-behavior-audit-preview',
     ),
     'maniphest.pkg.css' => array(
       'maniphest-task-summary-css',
     ),
     'maniphest.pkg.js' => array(
       'javelin-behavior-maniphest-batch-selector',
       'javelin-behavior-maniphest-subpriority-editor',
       'javelin-behavior-maniphest-list-editor',
     ),
   ),
 );
diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php
index 61f6176f15..d848fb81e8 100644
--- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php
+++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php
@@ -1,245 +1,247 @@
 <?php
 
 final class CelerityDefaultPostprocessor
   extends CelerityPostprocessor {
 
   const POSTPROCESSOR_KEY = 'default';
 
   public function getPostprocessorKey() {
     return self::POSTPROCESSOR_KEY;
   }
 
   public function getPostprocessorName() {
     return pht('Use Standard Colors');
   }
 
   public function buildDefaultPostprocessor() {
     return null;
   }
 
   public function buildVariables() {
     return array(
       // Fonts
       'basefont' => "13px 'Segoe UI', 'Segoe UI Emoji', ".
         "'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
         "Helvetica, Arial, sans-serif",
 
       'fontfamily' => "'Segoe UI', 'Segoe UI Emoji', ".
         "'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
         "Helvetica, Arial, sans-serif",
 
       // Drop Shadow
       'dropshadow' => '0 2px 12px rgba(0, 0, 0, .20)',
       'whitetextshadow' => '0 1px 0 rgba(255, 255, 255, 1)',
 
       // Anchors
       'anchor' => '#136CB2',
 
       // Font Sizes
       'biggestfontsize' => '15px',
       'biggerfontsize' => '14px',
       'normalfontsize' => '13px',
       'smallerfontsize' => '12px',
       'smallestfontsize' => '11px',
 
       // Base Colors
       'red'           => '#c0392b',
       'lightred'      => '#f4dddb',
       'orange'        => '#e67e22',
       'lightorange'   => '#f7e2d4',
       'yellow'        => '#f1c40f',
       'lightyellow'   => '#fdf5d4',
       'green'         => '#139543',
       'lightgreen'    => '#d7eddf',
       'blue'          => '#2980b9',
       'lightblue'     => '#daeaf3',
       'sky'           => '#3498db',
       'lightsky'      => '#ddeef9',
       'fire'          => '#e62f17',
       'indigo'        => '#6e5cb6',
       'lightindigo'   => '#eae6f7',
       'pink'          => '#da49be',
       'lightpink'     => '#fbeaf8',
       'violet'        => '#8e44ad',
       'lightviolet'   => '#ecdff1',
       'charcoal'      => '#4b4d51',
       'backdrop'      => '#c4cde0',
       'hoverwhite'    => 'rgba(255,255,255,.6)',
       'hovergrey'     => '#c5cbcf',
       'hoverblue'     => '#eceff5',
       'hoverborder'   => '#dfe1e9',
       'hoverselectedgrey' => '#bbc4ca',
       'hoverselectedblue' => '#e6e9ee',
       'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)',
       'timeline'    => '#d5d8e1',
       'timeline.icon.background' => '#E6E9F1',
       'bluepropertybackground' => '#eff3fc',
 
       // Alphas
       'alphawhite'          => '255,255,255',
       'alphagrey'           => '55,55,55',
       'alphablue'           => '71,87,120',
       'alphablack'          => '0,0,0',
 
       // Base Greys
       'lightgreyborder'     => '#C7CCD9',
       'greyborder'          => '#A1A6B0',
       'darkgreyborder'      => '#676A70',
       'lightgreytext'       => '#92969D',
       'greytext'            => '#74777D',
       'darkgreytext'        => '#4B4D51',
       'lightgreybackground' => '#F7F7F7',
       'greybackground'      => '#EBECEE',
       'darkgreybackground'  => '#DFE0E2',
 
       // Base Blues
       'thinblueborder'      => '#DDE8EF',
       'lightblueborder'     => '#BFCFDA',
       'blueborder'          => '#8C98B8',
       'darkblueborder'      => '#626E82',
       'lightbluebackground' => '#F8F9FC',
       'bluebackground'      => '#ECEEF4',
       'lightbluetext'       => '#8C98B8',
       'bluetext'            => '#6B748C',
       'darkbluetext'        => '#464C5C',
       'blacktext'           => '#000',
 
       // Base Greens
       'lightgreenborder'      => '#bfdac1',
       'greenborder'           => '#8cb89c',
       'greentext'             => '#3e6d35',
       'lightgreenbackground'  => '#e6f2e4',
 
       // Base Red
       'lightredborder'        => '#f4c6c6',
       'redborder'             => '#eb9797',
       'redtext'               => '#802b2b',
       'lightredbackground'    => '#f5e1e1',
 
       // Base Violet
       'lightvioletborder'     => '#cfbddb',
       'violetborder'          => '#b589ba',
       'violettext'            => '#603c73',
       'lightvioletbackground' => '#e9dfee',
 
       // Shades are a more muted set of our base colors
       // better suited to blending into other UIs.
 
       // Shade Red
       'sh-lightredborder'     => '#efcfcf',
       'sh-redborder'          => '#d1abab',
       'sh-redicon'            => '#c85a5a',
       'sh-redtext'            => '#a53737',
       'sh-redbackground'      => '#f7e6e6',
 
       // Shade Orange
       'sh-lightorangeborder'  => '#f8dcc3',
       'sh-orangeborder'       => '#dbb99e',
       'sh-orangeicon'         => '#e78331',
       'sh-orangetext'         => '#ba6016',
       'sh-orangebackground'   => '#fbede1',
 
       // Shade Yellow
       'sh-lightyellowborder'  => '#e9dbcd',
       'sh-yellowborder'       => '#c9b8a8',
       'sh-yellowicon'         => '#9b946e',
       'sh-yellowtext'         => '#726f56',
       'sh-yellowbackground'   => '#fdf3da',
 
       // Shade Green
       'sh-lightgreenborder'   => '#c6e6c7',
       'sh-greenborder'        => '#a0c4a1',
       'sh-greenicon'          => '#4ca74e',
       'sh-greentext'          => '#326d34',
       'sh-greenbackground'    => '#ddefdd',
 
       // Shade Blue
       'sh-lightblueborder'    => '#cfdbe3',
       'sh-blueborder'         => '#a7b5bf',
       'sh-blueicon'           => '#6b748c',
       'sh-bluetext'           => '#464c5c',
       'sh-bluebackground'     => '#dee7f8',
 
       // Shade Indigo
       'sh-lightindigoborder'  => '#d1c9ee',
       'sh-indigoborder'       => '#bcb4da',
       'sh-indigoicon'         => '#8672d4',
       'sh-indigotext'         => '#6e5cb6',
       'sh-indigobackground'   => '#eae6f7',
 
       // Shade Violet
       'sh-lightvioletborder'  => '#e0d1e7',
       'sh-violetborder'       => '#bcabc5',
       'sh-violeticon'         => '#9260ad',
       'sh-violettext'         => '#69427f',
       'sh-violetbackground'   => '#efe8f3',
 
       // Shade Pink
       'sh-lightpinkborder'  => '#f6d5ef',
       'sh-pinkborder'       => '#d5aecd',
       'sh-pinkicon'         => '#e26fcb',
       'sh-pinktext'         => '#da49be',
       'sh-pinkbackground'   => '#fbeaf8',
 
       // Shade Grey
       'sh-lightgreyborder'    => '#e3e4e8',
       'sh-greyborder'         => '#b2b2b2',
       'sh-greyicon'           => '#757575',
       'sh-greytext'           => '#555555',
       'sh-greybackground'     => '#edeef2',
 
       // Shade Disabled
       'sh-lightdisabledborder'  => '#e5e5e5',
       'sh-disabledborder'       => '#cbcbcb',
       'sh-disabledicon'         => '#bababa',
       'sh-disabledtext'         => '#a6a6a6',
       'sh-disabledbackground'   => '#f3f3f3',
 
       // Diffs
       'diff.background' => '#fff',
       'new-background' => 'rgba(151, 234, 151, .3)',
       'new-bright' => 'rgba(151, 234, 151, .6)',
+      'new-background-strong' => 'rgba(151, 234, 151, 1)',
       'old-background' => 'rgba(251, 175, 175, .3)',
       'old-bright' => 'rgba(251, 175, 175, .7)',
+      'old-background-strong' => 'rgba(251, 175, 175, 1)',
       'move-background' => '#fdf5d4',
       'copy-background' => '#f1c40f',
 
       // Usually light yellow
       'gentle.highlight' => '#fdf3da',
       'gentle.highlight.border' => '#c9b8a8',
 
       'paste.content' => '#fffef5',
       'paste.border' => '#e9dbcd',
       'paste.highlight' => '#fdf3da',
 
       // Background color for "most" themes.
       'page.background' => '#f3f5f7',
       'page.sidenav' => '#eaedf1',
       'page.content' => '#fff',
 
       'menu.profile.text' => 'rgba(255,255,255,.8)',
       'menu.profile.text.selected' => 'rgba(255,255,255,1)',
       'menu.profile.icon.disabled' => 'rgba(255,255,255,.4)',
 
       'menu.main.height' => '44px',
       'menu.profile.width' => '240px',
 
       // Buttons
       'blue.button.color' => '#2980b9',
       'blue.button.gradient' => 'linear-gradient(to bottom, #3498db, #2980b9)',
       'blue.button.hover' => 'linear-gradient(to bottom, #3498db, #1b6ba0)',
       'green.button.color' => '#139543',
       'green.button.gradient' => 'linear-gradient(to bottom, #23BB5B, #139543)',
       'green.button.hover' => 'linear-gradient(to bottom, #23BB5B, #178841)',
       'red.button.color' => '#b33225',
       'red.button.gradient' => 'linear-gradient(to bottom, #d25454, #b33225)',
       'red.button.hover' => 'linear-gradient(to bottom, #d25454, #982115)',
       'grey.button.color' => '#F7F7F9',
       'grey.button.gradient' => 'linear-gradient(to bottom, #ffffff, #f1f0f1)',
       'grey.button.hover' => 'linear-gradient(to bottom, #ffffff, #eeebec)',
 
 
     );
   }
 
 }
diff --git a/src/applications/differential/__tests__/data/whitespace.diff.one.whitespace b/src/applications/differential/__tests__/data/whitespace.diff.one.whitespace
index f4a5af6f3e..db43e02158 100644
--- a/src/applications/differential/__tests__/data/whitespace.diff.one.whitespace
+++ b/src/applications/differential/__tests__/data/whitespace.diff.one.whitespace
@@ -1,2 +1,2 @@
 O 1 - -=[-Rocket-Ship>\n~
-N 1 + {(          )}-=[-Rocket-Ship>\n~
+N 1 + {>          )}-=[-Rocket-Ship>\n~
diff --git a/src/applications/differential/__tests__/data/whitespace.diff.two.whitespace b/src/applications/differential/__tests__/data/whitespace.diff.two.whitespace
index f4a5af6f3e..db43e02158 100644
--- a/src/applications/differential/__tests__/data/whitespace.diff.two.whitespace
+++ b/src/applications/differential/__tests__/data/whitespace.diff.two.whitespace
@@ -1,2 +1,2 @@
 O 1 - -=[-Rocket-Ship>\n~
-N 1 + {(          )}-=[-Rocket-Ship>\n~
+N 1 + {>          )}-=[-Rocket-Ship>\n~
diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php
index 27d2a2d845..22f31b3e9f 100644
--- a/src/applications/differential/parser/DifferentialChangesetParser.php
+++ b/src/applications/differential/parser/DifferentialChangesetParser.php
@@ -1,1451 +1,1464 @@
 <?php
 
 final class DifferentialChangesetParser extends Phobject {
 
   const HIGHLIGHT_BYTE_LIMIT = 262144;
 
   protected $visible      = array();
   protected $new          = array();
   protected $old          = array();
   protected $intra        = array();
+  protected $depthOnlyLines = array();
   protected $newRender    = null;
   protected $oldRender    = null;
 
   protected $filename     = null;
   protected $hunkStartLines = array();
 
   protected $comments     = array();
   protected $specialAttributes = array();
 
   protected $changeset;
   protected $whitespaceMode = null;
 
   protected $renderCacheKey = null;
 
   private $handles = array();
   private $user;
 
   private $leftSideChangesetID;
   private $leftSideAttachesToNewFile;
 
   private $rightSideChangesetID;
   private $rightSideAttachesToNewFile;
 
   private $originalLeft;
   private $originalRight;
 
   private $renderingReference;
   private $isSubparser;
 
   private $isTopLevel;
 
   private $coverage;
   private $markupEngine;
   private $highlightErrors;
   private $disableCache;
   private $renderer;
   private $characterEncoding;
   private $highlightAs;
   private $highlightingDisabled;
   private $showEditAndReplyLinks = true;
   private $canMarkDone;
   private $objectOwnerPHID;
   private $offsetMode;
 
   private $rangeStart;
   private $rangeEnd;
   private $mask;
   private $linesOfContext = 8;
 
   private $highlightEngine;
 
   public function setRange($start, $end) {
     $this->rangeStart = $start;
     $this->rangeEnd = $end;
     return $this;
   }
 
   public function setMask(array $mask) {
     $this->mask = $mask;
     return $this;
   }
 
   public function renderChangeset() {
     return $this->render($this->rangeStart, $this->rangeEnd, $this->mask);
   }
 
   public function setShowEditAndReplyLinks($bool) {
     $this->showEditAndReplyLinks = $bool;
     return $this;
   }
 
   public function getShowEditAndReplyLinks() {
     return $this->showEditAndReplyLinks;
   }
 
   public function setHighlightAs($highlight_as) {
     $this->highlightAs = $highlight_as;
     return $this;
   }
 
   public function getHighlightAs() {
     return $this->highlightAs;
   }
 
   public function setCharacterEncoding($character_encoding) {
     $this->characterEncoding = $character_encoding;
     return $this;
   }
 
   public function getCharacterEncoding() {
     return $this->characterEncoding;
   }
 
   public function setRenderer(DifferentialChangesetRenderer $renderer) {
     $this->renderer = $renderer;
     return $this;
   }
 
   public function getRenderer() {
     if (!$this->renderer) {
       return new DifferentialChangesetTwoUpRenderer();
     }
     return $this->renderer;
   }
 
   public function setDisableCache($disable_cache) {
     $this->disableCache = $disable_cache;
     return $this;
   }
 
   public function getDisableCache() {
     return $this->disableCache;
   }
 
   public function setCanMarkDone($can_mark_done) {
     $this->canMarkDone = $can_mark_done;
     return $this;
   }
 
   public function getCanMarkDone() {
     return $this->canMarkDone;
   }
 
   public function setObjectOwnerPHID($phid) {
     $this->objectOwnerPHID = $phid;
     return $this;
   }
 
   public function getObjectOwnerPHID() {
     return $this->objectOwnerPHID;
   }
 
   public function setOffsetMode($offset_mode) {
     $this->offsetMode = $offset_mode;
     return $this;
   }
 
   public function getOffsetMode() {
     return $this->offsetMode;
   }
 
   public static function getDefaultRendererForViewer(PhabricatorUser $viewer) {
     $is_unified = $viewer->compareUserSetting(
       PhabricatorUnifiedDiffsSetting::SETTINGKEY,
       PhabricatorUnifiedDiffsSetting::VALUE_ALWAYS_UNIFIED);
 
     if ($is_unified) {
       return '1up';
     }
 
     return null;
   }
 
   public function readParametersFromRequest(AphrontRequest $request) {
     $this->setWhitespaceMode($request->getStr('whitespace'));
     $this->setCharacterEncoding($request->getStr('encoding'));
     $this->setHighlightAs($request->getStr('highlight'));
 
     $renderer = null;
 
     // If the viewer prefers unified diffs, always set the renderer to unified.
     // Otherwise, we leave it unspecified and the client will choose a
     // renderer based on the screen size.
 
     if ($request->getStr('renderer')) {
       $renderer = $request->getStr('renderer');
     } else {
       $renderer = self::getDefaultRendererForViewer($request->getViewer());
     }
 
     switch ($renderer) {
       case '1up':
         $this->setRenderer(new DifferentialChangesetOneUpRenderer());
         break;
       default:
         $this->setRenderer(new DifferentialChangesetTwoUpRenderer());
         break;
     }
 
     return $this;
   }
 
-  const CACHE_VERSION = 11;
+  const CACHE_VERSION = 12;
   const CACHE_MAX_SIZE = 8e6;
 
   const ATTR_GENERATED  = 'attr:generated';
   const ATTR_DELETED    = 'attr:deleted';
   const ATTR_UNCHANGED  = 'attr:unchanged';
   const ATTR_WHITELINES = 'attr:white';
   const ATTR_MOVEAWAY   = 'attr:moveaway';
 
   const WHITESPACE_SHOW_ALL         = 'show-all';
   const WHITESPACE_IGNORE_TRAILING  = 'ignore-trailing';
   const WHITESPACE_IGNORE_MOST      = 'ignore-most';
   const WHITESPACE_IGNORE_ALL       = 'ignore-all';
 
   public function setOldLines(array $lines) {
     $this->old = $lines;
     return $this;
   }
 
   public function setNewLines(array $lines) {
     $this->new = $lines;
     return $this;
   }
 
   public function setSpecialAttributes(array $attributes) {
     $this->specialAttributes = $attributes;
     return $this;
   }
 
   public function setIntraLineDiffs(array $diffs) {
     $this->intra = $diffs;
     return $this;
   }
 
+  public function setDepthOnlyLines(array $lines) {
+    $this->depthOnlyLines = $lines;
+    return $this;
+  }
+
+  public function getDepthOnlyLines() {
+    return $this->depthOnlyLines;
+  }
+
   public function setVisibileLinesMask(array $mask) {
     $this->visible = $mask;
     return $this;
   }
 
   public function setLinesOfContext($lines_of_context) {
     $this->linesOfContext = $lines_of_context;
     return $this;
   }
 
   public function getLinesOfContext() {
     return $this->linesOfContext;
   }
 
 
   /**
    * Configure which Changeset comments added to the right side of the visible
    * diff will be attached to. The ID must be the ID of a real Differential
    * Changeset.
    *
    * The complexity here is that we may show an arbitrary side of an arbitrary
    * changeset as either the left or right part of a diff. This method allows
    * the left and right halves of the displayed diff to be correctly mapped to
    * storage changesets.
    *
    * @param id    The Differential Changeset ID that comments added to the right
    *              side of the visible diff should be attached to.
    * @param bool  If true, attach new comments to the right side of the storage
    *              changeset. Note that this may be false, if the left side of
    *              some storage changeset is being shown as the right side of
    *              a display diff.
    * @return this
    */
   public function setRightSideCommentMapping($id, $is_new) {
     $this->rightSideChangesetID = $id;
     $this->rightSideAttachesToNewFile = $is_new;
     return $this;
   }
 
   /**
    * See setRightSideCommentMapping(), but this sets information for the left
    * side of the display diff.
    */
   public function setLeftSideCommentMapping($id, $is_new) {
     $this->leftSideChangesetID = $id;
     $this->leftSideAttachesToNewFile = $is_new;
     return $this;
   }
 
   public function setOriginals(
     DifferentialChangeset $left,
     DifferentialChangeset $right) {
 
     $this->originalLeft = $left;
     $this->originalRight = $right;
     return $this;
   }
 
   public function diffOriginals() {
     $engine = new PhabricatorDifferenceEngine();
     $changeset = $engine->generateChangesetFromFileContent(
       implode('', mpull($this->originalLeft->getHunks(), 'getChanges')),
       implode('', mpull($this->originalRight->getHunks(), 'getChanges')));
 
     $parser = new DifferentialHunkParser();
 
     return $parser->parseHunksForHighlightMasks(
       $changeset->getHunks(),
       $this->originalLeft->getHunks(),
       $this->originalRight->getHunks());
   }
 
   /**
    * Set a key for identifying this changeset in the render cache. If set, the
    * parser will attempt to use the changeset render cache, which can improve
    * performance for frequently-viewed changesets.
    *
    * By default, there is no render cache key and parsers do not use the cache.
    * This is appropriate for rarely-viewed changesets.
    *
    * NOTE: Currently, this key must be a valid Differential Changeset ID.
    *
    * @param   string  Key for identifying this changeset in the render cache.
    * @return  this
    */
   public function setRenderCacheKey($key) {
     $this->renderCacheKey = $key;
     return $this;
   }
 
   private function getRenderCacheKey() {
     return $this->renderCacheKey;
   }
 
   public function setChangeset(DifferentialChangeset $changeset) {
     $this->changeset = $changeset;
 
     $this->setFilename($changeset->getFilename());
 
     return $this;
   }
 
   public function setWhitespaceMode($whitespace_mode) {
     $this->whitespaceMode = $whitespace_mode;
     return $this;
   }
 
   public function setRenderingReference($ref) {
     $this->renderingReference = $ref;
     return $this;
   }
 
   private function getRenderingReference() {
     return $this->renderingReference;
   }
 
   public function getChangeset() {
     return $this->changeset;
   }
 
   public function setFilename($filename) {
     $this->filename = $filename;
     return $this;
   }
 
   public function setHandles(array $handles) {
     assert_instances_of($handles, 'PhabricatorObjectHandle');
     $this->handles = $handles;
     return $this;
   }
 
   public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
     $this->markupEngine = $engine;
     return $this;
   }
 
   public function setUser(PhabricatorUser $user) {
     $this->user = $user;
     return $this;
   }
 
   public function getUser() {
     return $this->user;
   }
 
   public function setCoverage($coverage) {
     $this->coverage = $coverage;
     return $this;
   }
   private function getCoverage() {
     return $this->coverage;
   }
 
   public function parseInlineComment(
     PhabricatorInlineCommentInterface $comment) {
 
     // Parse only comments which are actually visible.
     if ($this->isCommentVisibleOnRenderedDiff($comment)) {
       $this->comments[] = $comment;
     }
     return $this;
   }
 
   private function loadCache() {
     $render_cache_key = $this->getRenderCacheKey();
     if (!$render_cache_key) {
       return false;
     }
 
     $data = null;
 
     $changeset = new DifferentialChangeset();
     $conn_r = $changeset->establishConnection('r');
     $data = queryfx_one(
       $conn_r,
       'SELECT * FROM %T WHERE id = %d',
       $changeset->getTableName().'_parse_cache',
       $render_cache_key);
 
     if (!$data) {
       return false;
     }
 
     if ($data['cache'][0] == '{') {
       // This is likely an old-style JSON cache which we will not be able to
       // deserialize.
       return false;
     }
 
     $data = unserialize($data['cache']);
     if (!is_array($data) || !$data) {
       return false;
     }
 
     foreach (self::getCacheableProperties() as $cache_key) {
       if (!array_key_exists($cache_key, $data)) {
         // If we're missing a cache key, assume we're looking at an old cache
         // and ignore it.
         return false;
       }
     }
 
     if ($data['cacheVersion'] !== self::CACHE_VERSION) {
       return false;
     }
 
     // Someone displays contents of a partially cached shielded file.
     if (!isset($data['newRender']) && (!$this->isTopLevel || $this->comments)) {
       return false;
     }
 
     unset($data['cacheVersion'], $data['cacheHost']);
     $cache_prop = array_select_keys($data, self::getCacheableProperties());
     foreach ($cache_prop as $cache_key => $v) {
       $this->$cache_key = $v;
     }
 
     return true;
   }
 
   protected static function getCacheableProperties() {
     return array(
       'visible',
       'new',
       'old',
       'intra',
+      'depthOnlyLines',
       'newRender',
       'oldRender',
       'specialAttributes',
       'hunkStartLines',
       'cacheVersion',
       'cacheHost',
       'highlightingDisabled',
     );
   }
 
   public function saveCache() {
     if (PhabricatorEnv::isReadOnly()) {
       return false;
     }
 
     if ($this->highlightErrors) {
       return false;
     }
 
     $render_cache_key = $this->getRenderCacheKey();
     if (!$render_cache_key) {
       return false;
     }
 
     $cache = array();
     foreach (self::getCacheableProperties() as $cache_key) {
       switch ($cache_key) {
         case 'cacheVersion':
           $cache[$cache_key] = self::CACHE_VERSION;
           break;
         case 'cacheHost':
           $cache[$cache_key] = php_uname('n');
           break;
         default:
           $cache[$cache_key] = $this->$cache_key;
           break;
       }
     }
     $cache = serialize($cache);
 
     // We don't want to waste too much space by a single changeset.
     if (strlen($cache) > self::CACHE_MAX_SIZE) {
       return;
     }
 
     $changeset = new DifferentialChangeset();
     $conn_w = $changeset->establishConnection('w');
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       try {
         queryfx(
           $conn_w,
           'INSERT INTO %T (id, cache, dateCreated) VALUES (%d, %B, %d)
             ON DUPLICATE KEY UPDATE cache = VALUES(cache)',
           DifferentialChangeset::TABLE_CACHE,
           $render_cache_key,
           $cache,
           time());
       } catch (AphrontQueryException $ex) {
         // Ignore these exceptions. A common cause is that the cache is
         // larger than 'max_allowed_packet', in which case we're better off
         // not writing it.
 
         // TODO: It would be nice to tailor this more narrowly.
       }
     unset($unguarded);
   }
 
   private function markGenerated($new_corpus_block = '') {
     $generated_guess = (strpos($new_corpus_block, '@'.'generated') !== false);
 
     if (!$generated_guess) {
       $generated_path_regexps = PhabricatorEnv::getEnvConfig(
         'differential.generated-paths');
       foreach ($generated_path_regexps as $regexp) {
         if (preg_match($regexp, $this->changeset->getFilename())) {
           $generated_guess = true;
           break;
         }
       }
     }
 
     $event = new PhabricatorEvent(
       PhabricatorEventType::TYPE_DIFFERENTIAL_WILLMARKGENERATED,
       array(
         'corpus' => $new_corpus_block,
         'is_generated' => $generated_guess,
       )
     );
     PhutilEventEngine::dispatchEvent($event);
 
     $generated = $event->getValue('is_generated');
 
     $attribute = $this->changeset->isGeneratedChangeset();
     if ($attribute) {
       $generated = true;
     }
 
     $this->specialAttributes[self::ATTR_GENERATED] = $generated;
   }
 
   public function isGenerated() {
     return idx($this->specialAttributes, self::ATTR_GENERATED, false);
   }
 
   public function isDeleted() {
     return idx($this->specialAttributes, self::ATTR_DELETED, false);
   }
 
   public function isUnchanged() {
     return idx($this->specialAttributes, self::ATTR_UNCHANGED, false);
   }
 
   public function isWhitespaceOnly() {
     return idx($this->specialAttributes, self::ATTR_WHITELINES, false);
   }
 
   public function isMoveAway() {
     return idx($this->specialAttributes, self::ATTR_MOVEAWAY, false);
   }
 
   private function applyIntraline(&$render, $intra, $corpus) {
 
     foreach ($render as $key => $text) {
       if (isset($intra[$key])) {
         $render[$key] = ArcanistDiffUtils::applyIntralineDiff(
           $text,
           $intra[$key]);
       }
     }
   }
 
   private function getHighlightFuture($corpus) {
     $language = $this->highlightAs;
 
     if (!$language) {
       $language = $this->highlightEngine->getLanguageFromFilename(
         $this->filename);
 
       if (($language != 'txt') &&
           (strlen($corpus) > self::HIGHLIGHT_BYTE_LIMIT)) {
         $this->highlightingDisabled = true;
         $language = 'txt';
       }
     }
 
     return $this->highlightEngine->getHighlightFuture(
       $language,
       $corpus);
   }
 
   protected function processHighlightedSource($data, $result) {
 
     $result_lines = phutil_split_lines($result);
     foreach ($data as $key => $info) {
       if (!$info) {
         unset($result_lines[$key]);
       }
     }
     return $result_lines;
   }
 
   private function tryCacheStuff() {
     $whitespace_mode = $this->whitespaceMode;
     switch ($whitespace_mode) {
       case self::WHITESPACE_SHOW_ALL:
       case self::WHITESPACE_IGNORE_TRAILING:
       case self::WHITESPACE_IGNORE_ALL:
         break;
       default:
         $whitespace_mode = self::WHITESPACE_IGNORE_MOST;
         break;
     }
 
     $skip_cache = ($whitespace_mode != self::WHITESPACE_IGNORE_MOST);
     if ($this->disableCache) {
       $skip_cache = true;
     }
 
     if ($this->characterEncoding) {
       $skip_cache = true;
     }
 
     if ($this->highlightAs) {
       $skip_cache = true;
     }
 
     $this->whitespaceMode = $whitespace_mode;
 
     $changeset = $this->changeset;
 
     if ($changeset->getFileType() != DifferentialChangeType::FILE_TEXT &&
         $changeset->getFileType() != DifferentialChangeType::FILE_SYMLINK) {
 
       $this->markGenerated();
 
     } else {
       if ($skip_cache || !$this->loadCache()) {
         $this->process();
         if (!$skip_cache) {
           $this->saveCache();
         }
       }
     }
   }
 
   private function process() {
     $whitespace_mode = $this->whitespaceMode;
     $changeset = $this->changeset;
 
     $ignore_all = (($whitespace_mode == self::WHITESPACE_IGNORE_MOST) ||
                   ($whitespace_mode == self::WHITESPACE_IGNORE_ALL));
 
     $force_ignore = ($whitespace_mode == self::WHITESPACE_IGNORE_ALL);
 
     if (!$force_ignore) {
       if ($ignore_all && $changeset->getWhitespaceMatters()) {
         $ignore_all = false;
       }
     }
 
     // The "ignore all whitespace" algorithm depends on rediffing the
     // files, and we currently need complete representations of both
     // files to do anything reasonable. If we only have parts of the files,
     // don't use the "ignore all" algorithm.
     if ($ignore_all) {
       $hunks = $changeset->getHunks();
       if (count($hunks) !== 1) {
         $ignore_all = false;
       } else {
         $first_hunk = reset($hunks);
         if ($first_hunk->getOldOffset() != 1 ||
             $first_hunk->getNewOffset() != 1) {
             $ignore_all = false;
         }
       }
     }
 
     if ($ignore_all) {
       $old_file = $changeset->makeOldFile();
       $new_file = $changeset->makeNewFile();
       if ($old_file == $new_file) {
         // If the old and new files are exactly identical, the synthetic
         // diff below will give us nonsense and whitespace modes are
         // irrelevant anyway. This occurs when you, e.g., copy a file onto
         // itself in Subversion (see T271).
         $ignore_all = false;
       }
     }
 
     $hunk_parser = new DifferentialHunkParser();
     $hunk_parser->setWhitespaceMode($whitespace_mode);
     $hunk_parser->parseHunksForLineData($changeset->getHunks());
 
     // Depending on the whitespace mode, we may need to compute a different
     // set of changes than the set of changes in the hunk data (specifically,
     // we might want to consider changed lines which have only whitespace
     // changes as unchanged).
     if ($ignore_all) {
       $engine = new PhabricatorDifferenceEngine();
       $engine->setIgnoreWhitespace(true);
       $no_whitespace_changeset = $engine->generateChangesetFromFileContent(
         $old_file,
         $new_file);
 
       $type_parser = new DifferentialHunkParser();
       $type_parser->parseHunksForLineData($no_whitespace_changeset->getHunks());
 
       $hunk_parser->setOldLineTypeMap($type_parser->getOldLineTypeMap());
       $hunk_parser->setNewLineTypeMap($type_parser->getNewLineTypeMap());
     }
 
     $hunk_parser->reparseHunksForSpecialAttributes();
 
     $unchanged = false;
     if (!$hunk_parser->getHasAnyChanges()) {
       $filetype = $this->changeset->getFileType();
       if ($filetype == DifferentialChangeType::FILE_TEXT ||
           $filetype == DifferentialChangeType::FILE_SYMLINK) {
         $unchanged = true;
       }
     }
 
     $moveaway = false;
     $changetype = $this->changeset->getChangeType();
     if ($changetype == DifferentialChangeType::TYPE_MOVE_AWAY) {
       $moveaway = true;
     }
 
     $this->setSpecialAttributes(array(
       self::ATTR_UNCHANGED  => $unchanged,
       self::ATTR_DELETED    => $hunk_parser->getIsDeleted(),
       self::ATTR_WHITELINES => !$hunk_parser->getHasTextChanges(),
       self::ATTR_MOVEAWAY   => $moveaway,
     ));
 
     $lines_context = $this->getLinesOfContext();
 
     $hunk_parser->generateIntraLineDiffs();
     $hunk_parser->generateVisibileLinesMask($lines_context);
 
     $this->setOldLines($hunk_parser->getOldLines());
     $this->setNewLines($hunk_parser->getNewLines());
     $this->setIntraLineDiffs($hunk_parser->getIntraLineDiffs());
+    $this->setDepthOnlyLines($hunk_parser->getDepthOnlyLines());
     $this->setVisibileLinesMask($hunk_parser->getVisibleLinesMask());
     $this->hunkStartLines = $hunk_parser->getHunkStartLines(
       $changeset->getHunks());
 
     $new_corpus = $hunk_parser->getNewCorpus();
     $new_corpus_block = implode('', $new_corpus);
     $this->markGenerated($new_corpus_block);
 
     if ($this->isTopLevel &&
         !$this->comments &&
           ($this->isGenerated() ||
            $this->isUnchanged() ||
            $this->isDeleted())) {
       return;
     }
 
     $old_corpus = $hunk_parser->getOldCorpus();
     $old_corpus_block = implode('', $old_corpus);
     $old_future = $this->getHighlightFuture($old_corpus_block);
     $new_future = $this->getHighlightFuture($new_corpus_block);
     $futures = array(
       'old' => $old_future,
       'new' => $new_future,
     );
     $corpus_blocks = array(
       'old' => $old_corpus_block,
       'new' => $new_corpus_block,
     );
 
     $this->highlightErrors = false;
     foreach (new FutureIterator($futures) as $key => $future) {
       try {
         try {
           $highlighted = $future->resolve();
         } catch (PhutilSyntaxHighlighterException $ex) {
           $this->highlightErrors = true;
           $highlighted = id(new PhutilDefaultSyntaxHighlighter())
             ->getHighlightFuture($corpus_blocks[$key])
             ->resolve();
         }
         switch ($key) {
         case 'old':
           $this->oldRender = $this->processHighlightedSource(
             $this->old,
             $highlighted);
           break;
         case 'new':
           $this->newRender = $this->processHighlightedSource(
             $this->new,
             $highlighted);
           break;
         }
       } catch (Exception $ex) {
         phlog($ex);
         throw $ex;
       }
     }
 
     $this->applyIntraline(
       $this->oldRender,
       ipull($this->intra, 0),
       $old_corpus);
     $this->applyIntraline(
       $this->newRender,
       ipull($this->intra, 1),
       $new_corpus);
   }
 
   private function shouldRenderPropertyChangeHeader($changeset) {
     if (!$this->isTopLevel) {
       // We render properties only at top level; otherwise we get multiple
       // copies of them when a user clicks "Show More".
       return false;
     }
 
     return true;
   }
 
   public function render(
     $range_start  = null,
     $range_len    = null,
     $mask_force   = array()) {
 
     // "Top level" renders are initial requests for the whole file, versus
     // requests for a specific range generated by clicking "show more". We
     // generate property changes and "shield" UI elements only for toplevel
     // requests.
     $this->isTopLevel = (($range_start === null) && ($range_len === null));
     $this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine();
 
     $encoding = null;
     if ($this->characterEncoding) {
       // We are forcing this changeset to be interpreted with a specific
       // character encoding, so force all the hunks into that encoding and
       // propagate it to the renderer.
       $encoding = $this->characterEncoding;
       foreach ($this->changeset->getHunks() as $hunk) {
         $hunk->forceEncoding($this->characterEncoding);
       }
     } else {
       // We're just using the default, so tell the renderer what that is
       // (by reading the encoding from the first hunk).
       foreach ($this->changeset->getHunks() as $hunk) {
         $encoding = $hunk->getDataEncoding();
         break;
       }
     }
 
     $this->tryCacheStuff();
 
     // If we're rendering in an offset mode, treat the range numbers as line
     // numbers instead of rendering offsets.
     $offset_mode = $this->getOffsetMode();
     if ($offset_mode) {
       if ($offset_mode == 'new') {
         $offset_map = $this->new;
       } else {
         $offset_map = $this->old;
       }
 
       // NOTE: Inline comments use zero-based lengths. For example, a comment
       // that starts and ends on line 123 has length 0. Rendering considers
       // this range to have length 1. Probably both should agree, but that
       // ship likely sailed long ago. Tweak things here to get the two systems
       // to agree. See PHI985, where this affected mail rendering of inline
       // comments left on the final line of a file.
 
       $range_end = $this->getOffset($offset_map, $range_start + $range_len);
       $range_start = $this->getOffset($offset_map, $range_start);
       $range_len = ($range_end - $range_start) + 1;
     }
 
     $render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset);
 
     $rows = max(
       count($this->old),
       count($this->new));
 
     $renderer = $this->getRenderer()
       ->setUser($this->getUser())
       ->setChangeset($this->changeset)
       ->setRenderPropertyChangeHeader($render_pch)
       ->setIsTopLevel($this->isTopLevel)
       ->setOldRender($this->oldRender)
       ->setNewRender($this->newRender)
       ->setHunkStartLines($this->hunkStartLines)
       ->setOldChangesetID($this->leftSideChangesetID)
       ->setNewChangesetID($this->rightSideChangesetID)
       ->setOldAttachesToNewFile($this->leftSideAttachesToNewFile)
       ->setNewAttachesToNewFile($this->rightSideAttachesToNewFile)
       ->setCodeCoverage($this->getCoverage())
       ->setRenderingReference($this->getRenderingReference())
       ->setMarkupEngine($this->markupEngine)
       ->setHandles($this->handles)
       ->setOldLines($this->old)
       ->setNewLines($this->new)
       ->setOriginalCharacterEncoding($encoding)
       ->setShowEditAndReplyLinks($this->getShowEditAndReplyLinks())
       ->setCanMarkDone($this->getCanMarkDone())
       ->setObjectOwnerPHID($this->getObjectOwnerPHID())
-      ->setHighlightingDisabled($this->highlightingDisabled);
+      ->setHighlightingDisabled($this->highlightingDisabled)
+      ->setDepthOnlyLines($this->getDepthOnlyLines());
 
     $shield = null;
     if ($this->isTopLevel && !$this->comments) {
       if ($this->isGenerated()) {
         $shield = $renderer->renderShield(
           pht(
             'This file contains generated code, which does not normally '.
             'need to be reviewed.'));
       } else if ($this->isMoveAway()) {
         // We put an empty shield on these files. Normally, they do not have
         // any diff content anyway. However, if they come through `arc`, they
         // may have content. We don't want to show it (it's not useful) and
         // we bailed out of fully processing it earlier anyway.
 
         // We could show a message like "this file was moved", but we show
         // that as a change header anyway, so it would be redundant. Instead,
         // just render an empty shield to skip rendering the diff body.
         $shield = '';
       } else if ($this->isUnchanged()) {
         $type = 'text';
         if (!$rows) {
           // NOTE: Normally, diffs which don't change files do not include
           // file content (for example, if you "chmod +x" a file and then
           // run "git show", the file content is not available). Similarly,
           // if you move a file from A to B without changing it, diffs normally
           // do not show the file content. In some cases `arc` is able to
           // synthetically generate content for these diffs, but for raw diffs
           // we'll never have it so we need to be prepared to not render a link.
           $type = 'none';
         }
 
         $type_add = DifferentialChangeType::TYPE_ADD;
         if ($this->changeset->getChangeType() == $type_add) {
           // Although the generic message is sort of accurate in a technical
           // sense, this more-tailored message is less confusing.
           $shield = $renderer->renderShield(
             pht('This is an empty file.'),
             $type);
         } else {
           $shield = $renderer->renderShield(
             pht('The contents of this file were not changed.'),
             $type);
         }
       } else if ($this->isWhitespaceOnly()) {
         $shield = $renderer->renderShield(
           pht('This file was changed only by adding or removing whitespace.'),
           'whitespace');
       } else if ($this->isDeleted()) {
         $shield = $renderer->renderShield(
           pht('This file was completely deleted.'));
       } else if ($this->changeset->getAffectedLineCount() > 2500) {
         $shield = $renderer->renderShield(
           pht(
             'This file has a very large number of changes (%s lines).',
             new PhutilNumber($this->changeset->getAffectedLineCount())));
       }
     }
 
     if ($shield !== null) {
       return $renderer->renderChangesetTable($shield);
     }
 
     // This request should render the "undershield" headers if it's a top-level
     // request which made it this far (indicating the changeset has no shield)
     // or it's a request with no mask information (indicating it's the request
     // that removes the rendering shield). Possibly, this second class of
     // request might need to be made more explicit.
     $is_undershield = (empty($mask_force) || $this->isTopLevel);
     $renderer->setIsUndershield($is_undershield);
 
     $old_comments = array();
     $new_comments = array();
     $old_mask = array();
     $new_mask = array();
     $feedback_mask = array();
     $lines_context = $this->getLinesOfContext();
 
     if ($this->comments) {
       // If there are any comments which appear in sections of the file which
       // we don't have, we're going to move them backwards to the closest
       // earlier line. Two cases where this may happen are:
       //
       //   - Porting ghost comments forward into a file which was mostly
       //     deleted.
       //   - Porting ghost comments forward from a full-context diff to a
       //     partial-context diff.
 
       list($old_backmap, $new_backmap) = $this->buildLineBackmaps();
 
       foreach ($this->comments as $comment) {
         $new_side = $this->isCommentOnRightSideWhenDisplayed($comment);
 
         $line = $comment->getLineNumber();
         if ($new_side) {
           $back_line = $new_backmap[$line];
         } else {
           $back_line = $old_backmap[$line];
         }
 
         if ($back_line != $line) {
           // TODO: This should probably be cleaner, but just be simple and
           // obvious for now.
           $ghost = $comment->getIsGhost();
           if ($ghost) {
             $moved = pht(
               'This comment originally appeared on line %s, but that line '.
               'does not exist in this version of the diff. It has been '.
               'moved backward to the nearest line.',
               new PhutilNumber($line));
             $ghost['reason'] = $ghost['reason']."\n\n".$moved;
             $comment->setIsGhost($ghost);
           }
 
           $comment->setLineNumber($back_line);
           $comment->setLineLength(0);
 
         }
 
         $start = max($comment->getLineNumber() - $lines_context, 0);
         $end = $comment->getLineNumber() +
           $comment->getLineLength() +
           $lines_context;
         for ($ii = $start; $ii <= $end; $ii++) {
           if ($new_side) {
             $new_mask[$ii] = true;
           } else {
             $old_mask[$ii] = true;
           }
         }
       }
 
       foreach ($this->old as $ii => $old) {
         if (isset($old['line']) && isset($old_mask[$old['line']])) {
           $feedback_mask[$ii] = true;
         }
       }
 
       foreach ($this->new as $ii => $new) {
         if (isset($new['line']) && isset($new_mask[$new['line']])) {
           $feedback_mask[$ii] = true;
         }
       }
 
       $this->comments = id(new PHUIDiffInlineThreader())
         ->reorderAndThreadCommments($this->comments);
 
       foreach ($this->comments as $comment) {
         $final = $comment->getLineNumber() +
           $comment->getLineLength();
         $final = max(1, $final);
         if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
           $new_comments[$final][] = $comment;
         } else {
           $old_comments[$final][] = $comment;
         }
       }
     }
     $renderer
       ->setOldComments($old_comments)
       ->setNewComments($new_comments);
 
     switch ($this->changeset->getFileType()) {
       case DifferentialChangeType::FILE_IMAGE:
         $old = null;
         $new = null;
         // TODO: Improve the architectural issue as discussed in D955
         // https://secure.phabricator.com/D955
         $reference = $this->getRenderingReference();
         $parts = explode('/', $reference);
         if (count($parts) == 2) {
           list($id, $vs) = $parts;
         } else {
           $id = $parts[0];
           $vs = 0;
         }
         $id = (int)$id;
         $vs = (int)$vs;
 
         if (!$vs) {
           $metadata = $this->changeset->getMetadata();
           $data = idx($metadata, 'attachment-data');
 
           $old_phid = idx($metadata, 'old:binary-phid');
           $new_phid = idx($metadata, 'new:binary-phid');
         } else {
           $vs_changeset = id(new DifferentialChangeset())->load($vs);
           $old_phid = null;
           $new_phid = null;
 
           // TODO: This is spooky, see D6851
           if ($vs_changeset) {
             $vs_metadata = $vs_changeset->getMetadata();
             $old_phid = idx($vs_metadata, 'new:binary-phid');
           }
 
           $changeset = id(new DifferentialChangeset())->load($id);
           if ($changeset) {
             $metadata = $changeset->getMetadata();
             $new_phid = idx($metadata, 'new:binary-phid');
           }
         }
 
         if ($old_phid || $new_phid) {
           // grab the files, (micro) optimization for 1 query not 2
           $file_phids = array();
           if ($old_phid) {
             $file_phids[] = $old_phid;
           }
           if ($new_phid) {
             $file_phids[] = $new_phid;
           }
 
           $files = id(new PhabricatorFileQuery())
             ->setViewer($this->getUser())
             ->withPHIDs($file_phids)
             ->execute();
           foreach ($files as $file) {
             if (empty($file)) {
               continue;
             }
             if ($file->getPHID() == $old_phid) {
               $old = $file;
             } else if ($file->getPHID() == $new_phid) {
               $new = $file;
             }
           }
         }
 
         $renderer->attachOldFile($old);
         $renderer->attachNewFile($new);
 
         return $renderer->renderFileChange($old, $new, $id, $vs);
       case DifferentialChangeType::FILE_DIRECTORY:
       case DifferentialChangeType::FILE_BINARY:
         $output = $renderer->renderChangesetTable(null);
         return $output;
     }
 
     if ($this->originalLeft && $this->originalRight) {
       list($highlight_old, $highlight_new) = $this->diffOriginals();
       $highlight_old = array_flip($highlight_old);
       $highlight_new = array_flip($highlight_new);
       $renderer
         ->setHighlightOld($highlight_old)
         ->setHighlightNew($highlight_new);
     }
     $renderer
       ->setOriginalOld($this->originalLeft)
       ->setOriginalNew($this->originalRight);
 
     if ($range_start === null) {
       $range_start = 0;
     }
     if ($range_len === null) {
       $range_len = $rows;
     }
     $range_len = min($range_len, $rows - $range_start);
 
     list($gaps, $mask) = $this->calculateGapsAndMask(
       $mask_force,
       $feedback_mask,
       $range_start,
       $range_len);
 
     $renderer
       ->setGaps($gaps)
       ->setMask($mask);
 
     $html = $renderer->renderTextChange(
       $range_start,
       $range_len,
       $rows);
 
     return $renderer->renderChangesetTable($html);
   }
 
   /**
    * This function calculates a lot of stuff we need to know to display
    * the diff:
    *
    * Gaps - compute gaps in the visible display diff, where we will render
    * "Show more context" spacers. If a gap is smaller than the context size,
    * we just display it. Otherwise, we record it into $gaps and will render a
    * "show more context" element instead of diff text below. A given $gap
    * is a tuple of $gap_line_number_start and $gap_length.
    *
    * Mask - compute the actual lines that need to be shown (because they
    * are near changes lines, near inline comments, or the request has
    * explicitly asked for them, i.e. resulting from the user clicking
    * "show more"). The $mask returned is a sparsely populated dictionary
    * of $visible_line_number => true.
    *
    * @return array($gaps, $mask)
    */
   private function calculateGapsAndMask(
     $mask_force,
     $feedback_mask,
     $range_start,
     $range_len) {
 
     $lines_context = $this->getLinesOfContext();
 
     $gaps = array();
     $gap_start = 0;
     $in_gap = false;
     $base_mask = $this->visible + $mask_force + $feedback_mask;
     $base_mask[$range_start + $range_len] = true;
     for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) {
       if (isset($base_mask[$ii])) {
         if ($in_gap) {
           $gap_length = $ii - $gap_start;
           if ($gap_length <= $lines_context) {
             for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) {
               $base_mask[$jj] = true;
             }
           } else {
             $gaps[] = array($gap_start, $gap_length);
           }
           $in_gap = false;
         }
       } else {
         if (!$in_gap) {
           $gap_start = $ii;
           $in_gap = true;
         }
       }
     }
     $gaps = array_reverse($gaps);
     $mask = $base_mask;
 
     return array($gaps, $mask);
   }
 
   /**
    * Determine if an inline comment will appear on the rendered diff,
    * taking into consideration which halves of which changesets will actually
    * be shown.
    *
    * @param PhabricatorInlineCommentInterface Comment to test for visibility.
    * @return bool True if the comment is visible on the rendered diff.
    */
   private function isCommentVisibleOnRenderedDiff(
     PhabricatorInlineCommentInterface $comment) {
 
     $changeset_id = $comment->getChangesetID();
     $is_new = $comment->getIsNewFile();
 
     if ($changeset_id == $this->rightSideChangesetID &&
         $is_new == $this->rightSideAttachesToNewFile) {
         return true;
     }
 
     if ($changeset_id == $this->leftSideChangesetID &&
         $is_new == $this->leftSideAttachesToNewFile) {
         return true;
     }
 
     return false;
   }
 
 
   /**
    * Determine if a comment will appear on the right side of the display diff.
    * Note that the comment must appear somewhere on the rendered changeset, as
    * per isCommentVisibleOnRenderedDiff().
    *
    * @param PhabricatorInlineCommentInterface Comment to test for display
    *              location.
    * @return bool True for right, false for left.
    */
   private function isCommentOnRightSideWhenDisplayed(
     PhabricatorInlineCommentInterface $comment) {
 
     if (!$this->isCommentVisibleOnRenderedDiff($comment)) {
       throw new Exception(pht('Comment is not visible on changeset!'));
     }
 
     $changeset_id = $comment->getChangesetID();
     $is_new = $comment->getIsNewFile();
 
     if ($changeset_id == $this->rightSideChangesetID &&
         $is_new == $this->rightSideAttachesToNewFile) {
       return true;
     }
 
     return false;
   }
 
   /**
    * Parse the 'range' specification that this class and the client-side JS
    * emit to indicate that a user clicked "Show more..." on a diff. Generally,
    * use is something like this:
    *
    *   $spec = $request->getStr('range');
    *   $parsed = DifferentialChangesetParser::parseRangeSpecification($spec);
    *   list($start, $end, $mask) = $parsed;
    *   $parser->render($start, $end, $mask);
    *
    * @param string Range specification, indicating the range of the diff that
    *               should be rendered.
    * @return tuple List of <start, end, mask> suitable for passing to
    *               @{method:render}.
    */
   public static function parseRangeSpecification($spec) {
     $range_s = null;
     $range_e = null;
     $mask = array();
 
     if ($spec) {
       $match = null;
       if (preg_match('@^(\d+)-(\d+)(?:/(\d+)-(\d+))?$@', $spec, $match)) {
         $range_s = (int)$match[1];
         $range_e = (int)$match[2];
         if (count($match) > 3) {
           $start = (int)$match[3];
           $len = (int)$match[4];
           for ($ii = $start; $ii < $start + $len; $ii++) {
             $mask[$ii] = true;
           }
         }
       }
     }
 
     return array($range_s, $range_e, $mask);
   }
 
   /**
    * Render "modified coverage" information; test coverage on modified lines.
    * This synthesizes diff information with unit test information into a useful
    * indicator of how well tested a change is.
    */
   public function renderModifiedCoverage() {
     $na = phutil_tag('em', array(), '-');
 
     $coverage = $this->getCoverage();
     if (!$coverage) {
       return $na;
     }
 
     $covered = 0;
     $not_covered = 0;
 
     foreach ($this->new as $k => $new) {
       if (!$new['line']) {
         continue;
       }
 
       if (!$new['type']) {
         continue;
       }
 
       if (empty($coverage[$new['line'] - 1])) {
         continue;
       }
 
       switch ($coverage[$new['line'] - 1]) {
         case 'C':
           $covered++;
           break;
         case 'U':
           $not_covered++;
           break;
       }
     }
 
     if (!$covered && !$not_covered) {
       return $na;
     }
 
     return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
   }
 
   /**
    * Build maps from lines comments appear on to actual lines.
    */
   private function buildLineBackmaps() {
     $old_back = array();
     $new_back = array();
     foreach ($this->old as $ii => $old) {
       $old_back[$old['line']] = $old['line'];
     }
     foreach ($this->new as $ii => $new) {
       $new_back[$new['line']] = $new['line'];
     }
 
     $max_old_line = 0;
     $max_new_line = 0;
     foreach ($this->comments as $comment) {
       if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
         $max_new_line = max($max_new_line, $comment->getLineNumber());
       } else {
         $max_old_line = max($max_old_line, $comment->getLineNumber());
       }
     }
 
     $cursor = 1;
     for ($ii = 1; $ii <= $max_old_line; $ii++) {
       if (empty($old_back[$ii])) {
         $old_back[$ii] = $cursor;
       } else {
         $cursor = $old_back[$ii];
       }
     }
 
     $cursor = 1;
     for ($ii = 1; $ii <= $max_new_line; $ii++) {
       if (empty($new_back[$ii])) {
         $new_back[$ii] = $cursor;
       } else {
         $cursor = $new_back[$ii];
       }
     }
 
     return array($old_back, $new_back);
   }
 
   private function getOffset(array $map, $line) {
     if (!$map) {
       return null;
     }
 
     $line = (int)$line;
     foreach ($map as $key => $spec) {
       if ($spec && isset($spec['line'])) {
         if ((int)$spec['line'] >= $line) {
           return $key;
         }
       }
     }
 
     return $key;
   }
 
 }
diff --git a/src/applications/differential/parser/DifferentialHunkParser.php b/src/applications/differential/parser/DifferentialHunkParser.php
index 5bd98e9012..e7b9ce21a9 100644
--- a/src/applications/differential/parser/DifferentialHunkParser.php
+++ b/src/applications/differential/parser/DifferentialHunkParser.php
@@ -1,674 +1,839 @@
 <?php
 
 final class DifferentialHunkParser extends Phobject {
 
   private $oldLines;
   private $newLines;
   private $intraLineDiffs;
+  private $depthOnlyLines;
   private $visibleLinesMask;
   private $whitespaceMode;
 
   /**
    * Get a map of lines on which hunks start, other than line 1. This
    * datastructure is used to determine when to render "Context not available."
    * in diffs with multiple hunks.
    *
    * @return dict<int, bool>  Map of lines where hunks start, other than line 1.
    */
   public function getHunkStartLines(array $hunks) {
     assert_instances_of($hunks, 'DifferentialHunk');
 
     $map = array();
     foreach ($hunks as $hunk) {
       $line = $hunk->getOldOffset();
       if ($line > 1) {
         $map[$line] = true;
       }
     }
 
     return $map;
   }
 
   private function setVisibleLinesMask($mask) {
     $this->visibleLinesMask = $mask;
     return $this;
   }
   public function getVisibleLinesMask() {
     if ($this->visibleLinesMask === null) {
       throw new PhutilInvalidStateException('generateVisibileLinesMask');
     }
     return $this->visibleLinesMask;
   }
 
   private function setIntraLineDiffs($intra_line_diffs) {
     $this->intraLineDiffs = $intra_line_diffs;
     return $this;
   }
   public function getIntraLineDiffs() {
     if ($this->intraLineDiffs === null) {
       throw new PhutilInvalidStateException('generateIntraLineDiffs');
     }
     return $this->intraLineDiffs;
   }
 
   private function setNewLines($new_lines) {
     $this->newLines = $new_lines;
     return $this;
   }
   public function getNewLines() {
     if ($this->newLines === null) {
       throw new PhutilInvalidStateException('parseHunksForLineData');
     }
     return $this->newLines;
   }
 
   private function setOldLines($old_lines) {
     $this->oldLines = $old_lines;
     return $this;
   }
   public function getOldLines() {
     if ($this->oldLines === null) {
       throw new PhutilInvalidStateException('parseHunksForLineData');
     }
     return $this->oldLines;
   }
 
   public function getOldLineTypeMap() {
     $map = array();
     $old = $this->getOldLines();
     foreach ($old as $o) {
       if (!$o) {
         continue;
       }
       $map[$o['line']] = $o['type'];
     }
     return $map;
   }
 
   public function setOldLineTypeMap(array $map) {
     $lines = $this->getOldLines();
     foreach ($lines as $key => $data) {
       $lines[$key]['type'] = idx($map, $data['line']);
     }
     $this->oldLines = $lines;
     return $this;
   }
 
   public function getNewLineTypeMap() {
     $map = array();
     $new = $this->getNewLines();
     foreach ($new as $n) {
       if (!$n) {
         continue;
       }
       $map[$n['line']] = $n['type'];
     }
     return $map;
   }
 
   public function setNewLineTypeMap(array $map) {
     $lines = $this->getNewLines();
     foreach ($lines as $key => $data) {
       $lines[$key]['type'] = idx($map, $data['line']);
     }
     $this->newLines = $lines;
     return $this;
   }
 
+  public function setDepthOnlyLines(array $map) {
+    $this->depthOnlyLines = $map;
+    return $this;
+  }
+
+  public function getDepthOnlyLines() {
+    return $this->depthOnlyLines;
+  }
 
   public function setWhitespaceMode($white_space_mode) {
     $this->whitespaceMode = $white_space_mode;
     return $this;
   }
 
   private function getWhitespaceMode() {
     if ($this->whitespaceMode === null) {
       throw new Exception(
         pht(
           'You must %s before accessing this data.',
           'setWhitespaceMode'));
     }
     return $this->whitespaceMode;
   }
 
   public function getIsDeleted() {
     foreach ($this->getNewLines() as $line) {
       if ($line) {
         // At least one new line, so the entire file wasn't deleted.
         return false;
       }
     }
 
     foreach ($this->getOldLines() as $line) {
       if ($line) {
         // No new lines, at least one old line; the entire file was deleted.
         return true;
       }
     }
 
     // This is an empty file.
     return false;
   }
 
   /**
    * Returns true if the hunks change any text, not just whitespace.
    */
   public function getHasTextChanges() {
     return $this->getHasChanges('text');
   }
 
   /**
    * Returns true if the hunks change anything, including whitespace.
    */
   public function getHasAnyChanges() {
     return $this->getHasChanges('any');
   }
 
   private function getHasChanges($filter) {
     if ($filter !== 'any' && $filter !== 'text') {
       throw new Exception(pht("Unknown change filter '%s'.", $filter));
     }
 
     $old = $this->getOldLines();
     $new = $this->getNewLines();
 
     $is_any = ($filter === 'any');
 
     foreach ($old as $key => $o) {
       $n = $new[$key];
       if ($o === null || $n === null) {
         // One side is missing, and it's impossible for both sides to be null,
         // so the other side must have something, and thus the two sides are
         // different and the file has been changed under any type of filter.
         return true;
       }
 
       if ($o['type'] !== $n['type']) {
         // The types are different, so either the underlying text is actually
         // different or whatever whitespace rules we're using consider them
         // different.
         return true;
       }
 
       if ($o['text'] !== $n['text']) {
         if ($is_any) {
           // The text is different, so there's a change.
           return true;
         } else if (trim($o['text']) !== trim($n['text'])) {
           return true;
         }
       }
     }
 
     // No changes anywhere in the file.
     return false;
   }
 
 
   /**
    * This function takes advantage of the parsing work done in
    * @{method:parseHunksForLineData} and continues the struggle to hammer this
    * data into something we can display to a user.
    *
    * In particular, this function re-parses the hunks to make them equivalent
    * in length for easy rendering, adding `null` as necessary to pad the
    * length.
    *
    * Anyhoo, this function is not particularly well-named but I try.
    *
    * NOTE: this function must be called after
    * @{method:parseHunksForLineData}.
    */
   public function reparseHunksForSpecialAttributes() {
     $rebuild_old = array();
     $rebuild_new = array();
 
     $old_lines = array_reverse($this->getOldLines());
     $new_lines = array_reverse($this->getNewLines());
 
     while (count($old_lines) || count($new_lines)) {
       $old_line_data = array_pop($old_lines);
       $new_line_data = array_pop($new_lines);
 
       if ($old_line_data) {
         $o_type = $old_line_data['type'];
       } else {
         $o_type = null;
       }
 
       if ($new_line_data) {
         $n_type = $new_line_data['type'];
       } else {
         $n_type = null;
       }
 
       // This line does not exist in the new file.
       if (($o_type != null) && ($n_type == null)) {
         $rebuild_old[] = $old_line_data;
         $rebuild_new[] = null;
         if ($new_line_data) {
           array_push($new_lines, $new_line_data);
         }
         continue;
       }
 
       // This line does not exist in the old file.
       if (($n_type != null) && ($o_type == null)) {
         $rebuild_old[] = null;
         $rebuild_new[] = $new_line_data;
         if ($old_line_data) {
           array_push($old_lines, $old_line_data);
         }
         continue;
       }
 
       $rebuild_old[] = $old_line_data;
       $rebuild_new[] = $new_line_data;
     }
 
     $this->setOldLines($rebuild_old);
     $this->setNewLines($rebuild_new);
 
     $this->updateChangeTypesForWhitespaceMode();
 
     return $this;
   }
 
   private function updateChangeTypesForWhitespaceMode() {
     $mode = $this->getWhitespaceMode();
 
     $mode_show_all = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
     if ($mode === $mode_show_all) {
       // If we're showing all whitespace, we don't need to perform any updates.
       return;
     }
 
     $mode_trailing = DifferentialChangesetParser::WHITESPACE_IGNORE_TRAILING;
     $is_trailing = ($mode === $mode_trailing);
 
     $new = $this->getNewLines();
     $old = $this->getOldLines();
     foreach ($old as $key => $o) {
       $n = $new[$key];
 
       if (!$o || !$n) {
         continue;
       }
 
       if ($is_trailing) {
         // In "trailing" mode, we need to identify lines which are marked
         // changed but differ only by trailing whitespace. We mark these lines
         // unchanged.
         if ($o['type'] != $n['type']) {
           if (rtrim($o['text']) === rtrim($n['text'])) {
             $old[$key]['type'] = null;
             $new[$key]['type'] = null;
           }
         }
       } else {
         // In "ignore most" and "ignore all" modes, we need to identify lines
         // which are marked unchanged but have internal whitespace changes.
         // We want to ignore leading and trailing whitespace changes only, not
         // internal whitespace changes (`diff` doesn't have a mode for this, so
         // we have to fix it here). If the text is marked unchanged but the
         // old and new text differs by internal space, mark the lines changed.
         if ($o['type'] === null && $n['type'] === null) {
           if ($o['text'] !== $n['text']) {
             if (trim($o['text']) !== trim($n['text'])) {
               $old[$key]['type'] = '-';
               $new[$key]['type'] = '+';
             }
           }
         }
       }
     }
 
     $this->setOldLines($old);
     $this->setNewLines($new);
 
     return $this;
   }
 
   public function generateIntraLineDiffs() {
     $old = $this->getOldLines();
     $new = $this->getNewLines();
 
     $diffs = array();
+    $depth_only = array();
     foreach ($old as $key => $o) {
       $n = $new[$key];
 
       if (!$o || !$n) {
         continue;
       }
 
       if ($o['type'] != $n['type']) {
-        $diffs[$key] = ArcanistDiffUtils::generateIntralineDiff(
-          $o['text'],
-          $n['text']);
+        $o_segments = array();
+        $n_segments = array();
+        $tab_width = 2;
+
+        $o_text = $o['text'];
+        $n_text = $n['text'];
+
+        if ($o_text !== $n_text) {
+          $o_depth = $this->getIndentDepth($o_text, $tab_width);
+          $n_depth = $this->getIndentDepth($n_text, $tab_width);
+
+          if ($o_depth < $n_depth) {
+            $segment_type = '>';
+            $segment_width = $this->getCharacterCountForVisualWhitespace(
+              $n_text,
+              ($n_depth - $o_depth),
+              $tab_width);
+            if ($segment_width) {
+              $n_text = substr($n_text, $segment_width);
+              $n_segments[] = array(
+                $segment_type,
+                $segment_width,
+              );
+            }
+          } else if ($o_depth > $n_depth) {
+            $segment_type = '<';
+            $segment_width = $this->getCharacterCountForVisualWhitespace(
+              $o_text,
+              ($o_depth - $n_depth),
+              $tab_width);
+            if ($segment_width) {
+              $o_text = substr($o_text, $segment_width);
+              $o_segments[] = array(
+                $segment_type,
+                $segment_width,
+              );
+            }
+          }
+
+          // If there are no remaining changes to this line after we've marked
+          // off the indent depth changes, this line was only modified by
+          // changing the indent depth. Mark it for later so we can change how
+          // it is displayed.
+          if ($o_text === $n_text) {
+            $depth_only[$key] = $segment_type;
+          }
+        }
+
+        $intraline_segments = ArcanistDiffUtils::generateIntralineDiff(
+          $o_text,
+          $n_text);
+
+        foreach ($intraline_segments[0] as $o_segment) {
+          $o_segments[] = $o_segment;
+        }
+
+        foreach ($intraline_segments[1] as $n_segment) {
+          $n_segments[] = $n_segment;
+        }
+
+        $diffs[$key] = array(
+          $o_segments,
+          $n_segments,
+        );
       }
     }
 
     $this->setIntraLineDiffs($diffs);
+    $this->setDepthOnlyLines($depth_only);
 
     return $this;
   }
 
   public function generateVisibileLinesMask($lines_context) {
     $old = $this->getOldLines();
     $new = $this->getNewLines();
     $max_length = max(count($old), count($new));
     $visible = false;
     $last = 0;
     $mask = array();
     for ($cursor = -$lines_context; $cursor < $max_length; $cursor++) {
       $offset = $cursor + $lines_context;
       if ((isset($old[$offset]) && $old[$offset]['type']) ||
           (isset($new[$offset]) && $new[$offset]['type'])) {
         $visible = true;
         $last = $offset;
       } else if ($cursor > $last + $lines_context) {
         $visible = false;
       }
       if ($visible && $cursor > 0) {
         $mask[$cursor] = 1;
       }
     }
 
     $this->setVisibleLinesMask($mask);
 
     return $this;
   }
 
   public function getOldCorpus() {
     return $this->getCorpus($this->getOldLines());
   }
 
   public function getNewCorpus() {
     return $this->getCorpus($this->getNewLines());
   }
 
   private function getCorpus(array $lines) {
 
     $corpus = array();
     foreach ($lines as $l) {
       if ($l['type'] != '\\') {
         if ($l['text'] === null) {
           // There's no text on this side of the diff, but insert a placeholder
           // newline so the highlighted line numbers match up.
           $corpus[] = "\n";
         } else {
           $corpus[] = $l['text'];
         }
       }
     }
     return $corpus;
   }
 
   public function parseHunksForLineData(array $hunks) {
     assert_instances_of($hunks, 'DifferentialHunk');
 
     $old_lines = array();
     $new_lines = array();
     foreach ($hunks as $hunk) {
       $lines = $hunk->getSplitLines();
 
       $line_type_map = array();
       $line_text = array();
       foreach ($lines as $line_index => $line) {
         if (isset($line[0])) {
           $char = $line[0];
           switch ($char) {
             case ' ':
               $line_type_map[$line_index] = null;
               $line_text[$line_index] = substr($line, 1);
               break;
             case "\r":
             case "\n":
               // NOTE: Normally, the first character is a space, plus, minus or
               // backslash, but it may be a newline if it used to be a space and
               // trailing whitespace has been stripped via email transmission or
               // some similar mechanism. In these cases, we essentially pretend
               // the missing space is still there.
               $line_type_map[$line_index] = null;
               $line_text[$line_index] = $line;
               break;
             case '+':
             case '-':
             case '\\':
               $line_type_map[$line_index] = $char;
               $line_text[$line_index] = substr($line, 1);
               break;
             default:
               throw new Exception(
                 pht(
                   'Unexpected leading character "%s" at line index %s!',
                   $char,
                   $line_index));
           }
         } else {
           $line_type_map[$line_index] = null;
           $line_text[$line_index] = '';
         }
       }
 
       $old_line = $hunk->getOldOffset();
       $new_line = $hunk->getNewOffset();
 
       $num_lines = count($lines);
       for ($cursor = 0; $cursor < $num_lines; $cursor++) {
         $type = $line_type_map[$cursor];
         $data = array(
           'type'  => $type,
           'text'  => $line_text[$cursor],
           'line'  => $new_line,
         );
         if ($type == '\\') {
           $type = $line_type_map[$cursor - 1];
           $data['text'] = ltrim($data['text']);
         }
         switch ($type) {
           case '+':
             $new_lines[] = $data;
             ++$new_line;
             break;
           case '-':
             $data['line'] = $old_line;
             $old_lines[] = $data;
             ++$old_line;
             break;
           default:
             $new_lines[] = $data;
             $data['line'] = $old_line;
             $old_lines[] = $data;
             ++$new_line;
             ++$old_line;
             break;
         }
       }
     }
 
     $this->setOldLines($old_lines);
     $this->setNewLines($new_lines);
 
     return $this;
   }
 
   public function parseHunksForHighlightMasks(
     array $changeset_hunks,
     array $old_hunks,
     array $new_hunks) {
     assert_instances_of($changeset_hunks, 'DifferentialHunk');
     assert_instances_of($old_hunks,       'DifferentialHunk');
     assert_instances_of($new_hunks,       'DifferentialHunk');
 
     // Put changes side by side.
     $olds = array();
     $news = array();
     $olds_cursor = -1;
     $news_cursor = -1;
     foreach ($changeset_hunks as $hunk) {
       $n_old = $hunk->getOldOffset();
       $n_new = $hunk->getNewOffset();
       $changes = $hunk->getSplitLines();
       foreach ($changes as $line) {
         $diff_type = $line[0]; // Change type in diff of diffs.
         $orig_type = $line[1]; // Change type in the original diff.
         if ($diff_type == ' ') {
           // Use the same key for lines that are next to each other.
           if ($olds_cursor > $news_cursor) {
             $key = $olds_cursor + 1;
           } else {
             $key = $news_cursor + 1;
           }
           $olds[$key] = null;
           $news[$key] = null;
           $olds_cursor = $key;
           $news_cursor = $key;
         } else if ($diff_type == '-') {
           $olds[] = array($n_old, $orig_type);
           $olds_cursor++;
         } else if ($diff_type == '+') {
           $news[] = array($n_new, $orig_type);
           $news_cursor++;
         }
         if (($diff_type == '-' || $diff_type == ' ') && $orig_type != '-') {
           $n_old++;
         }
         if (($diff_type == '+' || $diff_type == ' ') && $orig_type != '-') {
           $n_new++;
         }
       }
     }
 
     $offsets_old = $this->computeOffsets($old_hunks);
     $offsets_new = $this->computeOffsets($new_hunks);
 
     // Highlight lines that were added on each side or removed on the other
     // side.
     $highlight_old = array();
     $highlight_new = array();
     $last = max(last_key($olds), last_key($news));
     for ($i = 0; $i <= $last; $i++) {
       if (isset($olds[$i])) {
         list($n, $type) = $olds[$i];
         if ($type == '+' ||
             ($type == ' ' && isset($news[$i]) && $news[$i][1] != ' ')) {
           $highlight_old[] = $offsets_old[$n];
         }
       }
       if (isset($news[$i])) {
         list($n, $type) = $news[$i];
         if ($type == '+' ||
             ($type == ' ' && isset($olds[$i]) && $olds[$i][1] != ' ')) {
           $highlight_new[] = $offsets_new[$n];
         }
       }
     }
 
     return array($highlight_old, $highlight_new);
   }
 
   public function makeContextDiff(
     array $hunks,
     $is_new,
     $line_number,
     $line_length,
     $add_context) {
 
     assert_instances_of($hunks, 'DifferentialHunk');
 
     $context = array();
 
     if ($is_new) {
       $prefix = '+';
     } else {
       $prefix = '-';
     }
 
     foreach ($hunks as $hunk) {
       if ($is_new) {
         $offset = $hunk->getNewOffset();
         $length = $hunk->getNewLen();
       } else {
         $offset = $hunk->getOldOffset();
         $length = $hunk->getOldLen();
       }
       $start = $line_number - $offset;
       $end = $start + $line_length;
       // We need to go in if $start == $length, because the last line
       // might be a "\No newline at end of file" marker, which we want
       // to show if the additional context is > 0.
       if ($start <= $length && $end >= 0) {
         $start = $start - $add_context;
         $end = $end + $add_context;
         $hunk_content = array();
         $hunk_pos = array('-' => 0, '+' => 0);
         $hunk_offset = array('-' => null, '+' => null);
         $hunk_last = array('-' => null, '+' => null);
         foreach (explode("\n", $hunk->getChanges()) as $line) {
           $in_common = strncmp($line, ' ', 1) === 0;
           $in_old = strncmp($line, '-', 1) === 0 || $in_common;
           $in_new = strncmp($line, '+', 1) === 0 || $in_common;
           $in_selected = strncmp($line, $prefix, 1) === 0;
           $skip = !$in_selected && !$in_common;
           if ($hunk_pos[$prefix] <= $end) {
             if ($start <= $hunk_pos[$prefix]) {
               if (!$skip || ($hunk_pos[$prefix] != $start &&
                 $hunk_pos[$prefix] != $end)) {
                   if ($in_old) {
                     if ($hunk_offset['-'] === null) {
                       $hunk_offset['-'] = $hunk_pos['-'];
                     }
                     $hunk_last['-'] = $hunk_pos['-'];
                   }
                   if ($in_new) {
                     if ($hunk_offset['+'] === null) {
                       $hunk_offset['+'] = $hunk_pos['+'];
                     }
                     $hunk_last['+'] = $hunk_pos['+'];
                   }
 
                   $hunk_content[] = $line;
                 }
             }
             if ($in_old) { ++$hunk_pos['-']; }
             if ($in_new) { ++$hunk_pos['+']; }
           }
         }
         if ($hunk_offset['-'] !== null || $hunk_offset['+'] !== null) {
           $header = '@@';
           if ($hunk_offset['-'] !== null) {
             $header .= ' -'.($hunk->getOldOffset() + $hunk_offset['-']).
               ','.($hunk_last['-'] - $hunk_offset['-'] + 1);
           }
           if ($hunk_offset['+'] !== null) {
             $header .= ' +'.($hunk->getNewOffset() + $hunk_offset['+']).
               ','.($hunk_last['+'] - $hunk_offset['+'] + 1);
           }
           $header .= ' @@';
           $context[] = $header;
           $context[] = implode("\n", $hunk_content);
         }
       }
     }
     return implode("\n", $context);
   }
 
   private function computeOffsets(array $hunks) {
     assert_instances_of($hunks, 'DifferentialHunk');
 
     $offsets = array();
     $n = 1;
     foreach ($hunks as $hunk) {
       $new_length = $hunk->getNewLen();
       $new_offset = $hunk->getNewOffset();
 
       for ($i = 0; $i < $new_length; $i++) {
         $offsets[$n] = $new_offset + $i;
         $n++;
       }
     }
 
     return $offsets;
   }
+
+  private function getIndentDepth($text, $tab_width) {
+    $len = strlen($text);
+
+    $depth = 0;
+    for ($ii = 0; $ii < $len; $ii++) {
+      $c = $text[$ii];
+
+      // If this is a space, increase the indent depth by 1.
+      if ($c == ' ') {
+        $depth++;
+        continue;
+      }
+
+      // If this is a tab, increase the indent depth to the next tabstop.
+
+      // For example, if the tab width is 4, these sequences both lead us to
+      // a visual width of 8, i.e. the cursor will be in the 8th column:
+      //
+      //   <tab><tab>
+      //   <space><tab><space><space><space><tab>
+
+      if ($c == "\t") {
+        $depth = ($depth + $tab_width);
+        $depth = $depth - ($depth % $tab_width);
+        continue;
+      }
+
+      break;
+    }
+
+    return $depth;
+  }
+
+  private function getCharacterCountForVisualWhitespace(
+    $text,
+    $depth,
+    $tab_width) {
+
+    // Here, we know the visual indent depth of a line has been increased by
+    // some amount (for example, 6 characters).
+
+    // We want to find the largest whitespace prefix of the string we can
+    // which still fits into that amount of visual space.
+
+    // In most cases, this is very easy. For example, if the string has been
+    // indented by two characters and the string begins with two spaces, that's
+    // a perfect match.
+
+    // However, if the string has been indented by 7 characters, the tab width
+    // is 8, and the string begins with "<space><space><tab>", we can only
+    // mark the two spaces as an indent change. These cases are unusual.
+
+    $character_depth = 0;
+    $visual_depth = 0;
+
+    $len = strlen($text);
+    for ($ii = 0; $ii < $len; $ii++) {
+      if ($visual_depth >= $depth) {
+        break;
+      }
+
+      $c = $text[$ii];
+
+      if ($c == ' ') {
+        $character_depth++;
+        $visual_depth++;
+        continue;
+      }
+
+      if ($c == "\t") {
+        // Figure out how many visual spaces we have until the next tabstop.
+        $tab_visual = ($visual_depth + $tab_width);
+        $tab_visual = $tab_visual - ($tab_visual % $tab_width);
+        $tab_visual = ($tab_visual - $visual_depth);
+
+        // If this tab would take us over the limit, we're all done.
+        $remaining_depth = ($depth - $visual_depth);
+        if ($remaining_depth < $tab_visual) {
+          break;
+        }
+
+        $character_depth++;
+        $visual_depth += $tab_visual;
+        continue;
+      }
+
+      break;
+    }
+
+    return $character_depth;
+  }
+
 }
diff --git a/src/applications/differential/render/DifferentialChangesetRenderer.php b/src/applications/differential/render/DifferentialChangesetRenderer.php
index f295695286..c5d033a4a9 100644
--- a/src/applications/differential/render/DifferentialChangesetRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetRenderer.php
@@ -1,701 +1,711 @@
 <?php
 
 abstract class DifferentialChangesetRenderer extends Phobject {
 
   private $user;
   private $changeset;
   private $renderingReference;
   private $renderPropertyChangeHeader;
   private $isTopLevel;
   private $isUndershield;
   private $hunkStartLines;
   private $oldLines;
   private $newLines;
   private $oldComments;
   private $newComments;
   private $oldChangesetID;
   private $newChangesetID;
   private $oldAttachesToNewFile;
   private $newAttachesToNewFile;
   private $highlightOld = array();
   private $highlightNew = array();
   private $codeCoverage;
   private $handles;
   private $markupEngine;
   private $oldRender;
   private $newRender;
   private $originalOld;
   private $originalNew;
   private $gaps;
   private $mask;
   private $originalCharacterEncoding;
   private $showEditAndReplyLinks;
   private $canMarkDone;
   private $objectOwnerPHID;
   private $highlightingDisabled;
   private $scopeEngine;
+  private $depthOnlyLines;
 
   private $oldFile = false;
   private $newFile = false;
 
   abstract public function getRendererKey();
 
   public function setShowEditAndReplyLinks($bool) {
     $this->showEditAndReplyLinks = $bool;
     return $this;
   }
 
   public function getShowEditAndReplyLinks() {
     return $this->showEditAndReplyLinks;
   }
 
   public function setHighlightingDisabled($highlighting_disabled) {
     $this->highlightingDisabled = $highlighting_disabled;
     return $this;
   }
 
   public function getHighlightingDisabled() {
     return $this->highlightingDisabled;
   }
 
   public function setOriginalCharacterEncoding($original_character_encoding) {
     $this->originalCharacterEncoding = $original_character_encoding;
     return $this;
   }
 
   public function getOriginalCharacterEncoding() {
     return $this->originalCharacterEncoding;
   }
 
   public function setIsUndershield($is_undershield) {
     $this->isUndershield = $is_undershield;
     return $this;
   }
 
   public function getIsUndershield() {
     return $this->isUndershield;
   }
 
   public function setMask($mask) {
     $this->mask = $mask;
     return $this;
   }
   protected function getMask() {
     return $this->mask;
   }
 
   public function setGaps($gaps) {
     $this->gaps = $gaps;
     return $this;
   }
   protected function getGaps() {
     return $this->gaps;
   }
 
+  public function setDepthOnlyLines(array $lines) {
+    $this->depthOnlyLines = $lines;
+    return $this;
+  }
+
+  public function getDepthOnlyLines() {
+    return $this->depthOnlyLines;
+  }
+
   public function attachOldFile(PhabricatorFile $old = null) {
     $this->oldFile = $old;
     return $this;
   }
 
   public function getOldFile() {
     if ($this->oldFile === false) {
       throw new PhabricatorDataNotAttachedException($this);
     }
     return $this->oldFile;
   }
 
   public function hasOldFile() {
     return (bool)$this->oldFile;
   }
 
   public function attachNewFile(PhabricatorFile $new = null) {
     $this->newFile = $new;
     return $this;
   }
 
   public function getNewFile() {
     if ($this->newFile === false) {
       throw new PhabricatorDataNotAttachedException($this);
     }
     return $this->newFile;
   }
 
   public function hasNewFile() {
     return (bool)$this->newFile;
   }
 
   public function setOriginalNew($original_new) {
     $this->originalNew = $original_new;
     return $this;
   }
   protected function getOriginalNew() {
     return $this->originalNew;
   }
 
   public function setOriginalOld($original_old) {
     $this->originalOld = $original_old;
     return $this;
   }
   protected function getOriginalOld() {
     return $this->originalOld;
   }
 
   public function setNewRender($new_render) {
     $this->newRender = $new_render;
     return $this;
   }
   protected function getNewRender() {
     return $this->newRender;
   }
 
   public function setOldRender($old_render) {
     $this->oldRender = $old_render;
     return $this;
   }
   protected function getOldRender() {
     return $this->oldRender;
   }
 
   public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) {
     $this->markupEngine = $markup_engine;
     return $this;
   }
   public function getMarkupEngine() {
     return $this->markupEngine;
   }
 
   public function setHandles(array $handles) {
     assert_instances_of($handles, 'PhabricatorObjectHandle');
     $this->handles = $handles;
     return $this;
   }
   protected function getHandles() {
     return $this->handles;
   }
 
   public function setCodeCoverage($code_coverage) {
     $this->codeCoverage = $code_coverage;
     return $this;
   }
   protected function getCodeCoverage() {
     return $this->codeCoverage;
   }
 
   public function setHighlightNew($highlight_new) {
     $this->highlightNew = $highlight_new;
     return $this;
   }
   protected function getHighlightNew() {
     return $this->highlightNew;
   }
 
   public function setHighlightOld($highlight_old) {
     $this->highlightOld = $highlight_old;
     return $this;
   }
   protected function getHighlightOld() {
     return $this->highlightOld;
   }
 
   public function setNewAttachesToNewFile($attaches) {
     $this->newAttachesToNewFile = $attaches;
     return $this;
   }
   protected function getNewAttachesToNewFile() {
     return $this->newAttachesToNewFile;
   }
 
   public function setOldAttachesToNewFile($attaches) {
     $this->oldAttachesToNewFile = $attaches;
     return $this;
   }
   protected function getOldAttachesToNewFile() {
     return $this->oldAttachesToNewFile;
   }
 
   public function setNewChangesetID($new_changeset_id) {
     $this->newChangesetID = $new_changeset_id;
     return $this;
   }
   protected function getNewChangesetID() {
     return $this->newChangesetID;
   }
 
   public function setOldChangesetID($old_changeset_id) {
     $this->oldChangesetID = $old_changeset_id;
     return $this;
   }
   protected function getOldChangesetID() {
     return $this->oldChangesetID;
   }
 
   public function setNewComments(array $new_comments) {
     foreach ($new_comments as $line_number => $comments) {
       assert_instances_of($comments, 'PhabricatorInlineCommentInterface');
     }
     $this->newComments = $new_comments;
     return $this;
   }
   protected function getNewComments() {
     return $this->newComments;
   }
 
   public function setOldComments(array $old_comments) {
     foreach ($old_comments as $line_number => $comments) {
       assert_instances_of($comments, 'PhabricatorInlineCommentInterface');
     }
     $this->oldComments = $old_comments;
     return $this;
   }
   protected function getOldComments() {
     return $this->oldComments;
   }
 
   public function setNewLines(array $new_lines) {
     $this->newLines = $new_lines;
     return $this;
   }
   protected function getNewLines() {
     return $this->newLines;
   }
 
   public function setOldLines(array $old_lines) {
     $this->oldLines = $old_lines;
     return $this;
   }
   protected function getOldLines() {
     return $this->oldLines;
   }
 
   public function setHunkStartLines(array $hunk_start_lines) {
     $this->hunkStartLines = $hunk_start_lines;
     return $this;
   }
 
   protected function getHunkStartLines() {
     return $this->hunkStartLines;
   }
 
   public function setUser(PhabricatorUser $user) {
     $this->user = $user;
     return $this;
   }
   protected function getUser() {
     return $this->user;
   }
 
   public function setChangeset(DifferentialChangeset $changeset) {
     $this->changeset = $changeset;
     return $this;
   }
   protected function getChangeset() {
     return $this->changeset;
   }
 
   public function setRenderingReference($rendering_reference) {
     $this->renderingReference = $rendering_reference;
     return $this;
   }
   protected function getRenderingReference() {
     return $this->renderingReference;
   }
 
   public function setRenderPropertyChangeHeader($should_render) {
     $this->renderPropertyChangeHeader = $should_render;
     return $this;
   }
 
   private function shouldRenderPropertyChangeHeader() {
     return $this->renderPropertyChangeHeader;
   }
 
   public function setIsTopLevel($is) {
     $this->isTopLevel = $is;
     return $this;
   }
 
   private function getIsTopLevel() {
     return $this->isTopLevel;
   }
 
   public function setCanMarkDone($can_mark_done) {
     $this->canMarkDone = $can_mark_done;
     return $this;
   }
 
   public function getCanMarkDone() {
     return $this->canMarkDone;
   }
 
   public function setObjectOwnerPHID($phid) {
     $this->objectOwnerPHID = $phid;
     return $this;
   }
 
   public function getObjectOwnerPHID() {
     return $this->objectOwnerPHID;
   }
 
   final public function renderChangesetTable($content) {
     $props = null;
     if ($this->shouldRenderPropertyChangeHeader()) {
       $props = $this->renderPropertyChangeHeader();
     }
 
     $notice = null;
     if ($this->getIsTopLevel()) {
       $force = (!$content && !$props);
       $notice = $this->renderChangeTypeHeader($force);
     }
 
     $undershield = null;
     if ($this->getIsUndershield()) {
       $undershield = $this->renderUndershieldHeader();
     }
 
     $result = $notice.$props.$undershield.$content;
 
     // TODO: Let the user customize their tab width / display style.
     // TODO: We should possibly post-process "\r" as well.
     // TODO: Both these steps should happen earlier.
     $result = str_replace("\t", '  ', $result);
 
     return phutil_safe_html($result);
   }
 
   abstract public function isOneUpRenderer();
   abstract public function renderTextChange(
     $range_start,
     $range_len,
     $rows);
   abstract public function renderFileChange(
     $old = null,
     $new = null,
     $id = 0,
     $vs = 0);
 
   abstract protected function renderChangeTypeHeader($force);
   abstract protected function renderUndershieldHeader();
 
   protected function didRenderChangesetTableContents($contents) {
     return $contents;
   }
 
   /**
    * Render a "shield" over the diff, with a message like "This file is
    * generated and does not need to be reviewed." or "This file was completely
    * deleted." This UI element hides unimportant text so the reviewer doesn't
    * need to scroll past it.
    *
    * The shield includes a link to view the underlying content. This link
    * may force certain rendering modes when the link is clicked:
    *
    *    - `"default"`: Render the diff normally, as though it was not
    *      shielded. This is the default and appropriate if the underlying
    *      diff is a normal change, but was hidden for reasons of not being
    *      important (e.g., generated code).
    *    - `"text"`: Force the text to be shown. This is probably only relevant
    *      when a file is not changed.
    *    - `"whitespace"`: Force the text to be shown, and the diff to be
    *      rendered with all whitespace shown. This is probably only relevant
    *      when a file is changed only by altering whitespace.
    *    - `"none"`: Don't show the link (e.g., text not available).
    *
    * @param   string        Message explaining why the diff is hidden.
    * @param   string|null   Force mode, see above.
    * @return  string        Shield markup.
    */
   abstract public function renderShield($message, $force = 'default');
 
   abstract protected function renderPropertyChangeHeader();
 
   protected function buildPrimitives($range_start, $range_len) {
     $primitives = array();
 
     $hunk_starts = $this->getHunkStartLines();
 
     $mask = $this->getMask();
     $gaps = $this->getGaps();
 
     $old = $this->getOldLines();
     $new = $this->getNewLines();
     $old_render = $this->getOldRender();
     $new_render = $this->getNewRender();
     $old_comments = $this->getOldComments();
     $new_comments = $this->getNewComments();
 
     $size = count($old);
     for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
       if (empty($mask[$ii])) {
         list($top, $len) = array_pop($gaps);
         $primitives[] = array(
           'type' => 'context',
           'top' => $top,
           'len' => $len,
         );
 
         $ii += ($len - 1);
         continue;
       }
 
       $ospec = array(
         'type' => 'old',
         'htype' => null,
         'cursor' => $ii,
         'line' => null,
         'oline' => null,
         'render' => null,
       );
 
       $nspec = array(
         'type' => 'new',
         'htype' => null,
         'cursor' => $ii,
         'line' => null,
         'oline' => null,
         'render' => null,
         'copy' => null,
         'coverage' => null,
       );
 
       if (isset($old[$ii])) {
         $ospec['line'] = (int)$old[$ii]['line'];
         $nspec['oline'] = (int)$old[$ii]['line'];
         $ospec['htype'] = $old[$ii]['type'];
         if (isset($old_render[$ii])) {
           $ospec['render'] = $old_render[$ii];
         }
       }
 
       if (isset($new[$ii])) {
         $nspec['line'] = (int)$new[$ii]['line'];
         $ospec['oline'] = (int)$new[$ii]['line'];
         $nspec['htype'] = $new[$ii]['type'];
         if (isset($new_render[$ii])) {
           $nspec['render'] = $new_render[$ii];
         }
       }
 
       if (isset($hunk_starts[$ospec['line']])) {
         $primitives[] = array(
           'type' => 'no-context',
         );
       }
 
       $primitives[] = $ospec;
       $primitives[] = $nspec;
 
       if ($ospec['line'] !== null && isset($old_comments[$ospec['line']])) {
         foreach ($old_comments[$ospec['line']] as $comment) {
           $primitives[] = array(
             'type' => 'inline',
             'comment' => $comment,
             'right' => false,
           );
         }
       }
 
       if ($nspec['line'] !== null && isset($new_comments[$nspec['line']])) {
         foreach ($new_comments[$nspec['line']] as $comment) {
           $primitives[] = array(
             'type' => 'inline',
             'comment' => $comment,
             'right' => true,
           );
         }
       }
 
       if ($hunk_starts && ($ii == $size - 1)) {
         $primitives[] = array(
           'type' => 'no-context',
         );
       }
     }
 
     if ($this->isOneUpRenderer()) {
       $primitives = $this->processPrimitivesForOneUp($primitives);
     }
 
     return $primitives;
   }
 
   private function processPrimitivesForOneUp(array $primitives) {
     // Primitives come out of buildPrimitives() in two-up format, because it
     // is the most general, flexible format. To put them into one-up format,
     // we need to filter and reorder them. In particular:
     //
     //   - We discard unchanged lines in the old file; in one-up format, we
     //     render them only once.
     //   - We group contiguous blocks of old-modified and new-modified lines, so
     //     they render in "block of old, block of new" order instead of
     //     alternating old and new lines.
 
     $out = array();
 
     $old_buf = array();
     $new_buf = array();
     foreach ($primitives as $primitive) {
       $type = $primitive['type'];
 
       if ($type == 'old') {
         if (!$primitive['htype']) {
           // This is a line which appears in both the old file and the new
           // file, or the spacer corresponding to a line added in the new file.
           // Ignore it when rendering a one-up diff.
           continue;
         }
         $old_buf[] = $primitive;
       } else if ($type == 'new') {
         if ($primitive['line'] === null) {
           // This is an empty spacer corresponding to a line removed from the
           // old file. Ignore it when rendering a one-up diff.
           continue;
         }
         if (!$primitive['htype']) {
           // If this line is the same in both versions of the file, put it in
           // the old line buffer. This makes sure inlines on old, unchanged
           // lines end up in the right place.
 
           // First, we need to flush the line buffers if they're not empty.
           if ($old_buf) {
             $out[] = $old_buf;
             $old_buf = array();
           }
           if ($new_buf) {
             $out[] = $new_buf;
             $new_buf = array();
           }
           $old_buf[] = $primitive;
         } else {
           $new_buf[] = $primitive;
         }
       } else if ($type == 'context' || $type == 'no-context') {
         $out[] = $old_buf;
         $out[] = $new_buf;
         $old_buf = array();
         $new_buf = array();
         $out[] = array($primitive);
       } else if ($type == 'inline') {
 
         // If this inline is on the left side, put it after the old lines.
         if (!$primitive['right']) {
           $out[] = $old_buf;
           $out[] = array($primitive);
           $old_buf = array();
         } else {
           $out[] = $old_buf;
           $out[] = $new_buf;
           $out[] = array($primitive);
           $old_buf = array();
           $new_buf = array();
         }
 
       } else {
         throw new Exception(pht("Unknown primitive type '%s'!", $primitive));
       }
     }
 
     $out[] = $old_buf;
     $out[] = $new_buf;
     $out = array_mergev($out);
 
     return $out;
   }
 
   protected function getChangesetProperties($changeset) {
     $old = $changeset->getOldProperties();
     $new = $changeset->getNewProperties();
 
     // When adding files, don't show the uninteresting 644 filemode change.
     if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD &&
         $new == array('unix:filemode' => '100644')) {
       unset($new['unix:filemode']);
     }
 
     // Likewise when removing files.
     if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE &&
         $old == array('unix:filemode' => '100644')) {
       unset($old['unix:filemode']);
     }
 
     $metadata = $changeset->getMetadata();
 
     if ($this->hasOldFile()) {
       $file = $this->getOldFile();
       if ($file->getImageWidth()) {
         $dimensions = $file->getImageWidth().'x'.$file->getImageHeight();
         $old['file:dimensions'] = $dimensions;
       }
       $old['file:mimetype'] = $file->getMimeType();
       $old['file:size'] = phutil_format_bytes($file->getByteSize());
     } else {
       $old['file:mimetype'] = idx($metadata, 'old:file:mime-type');
       $size = idx($metadata, 'old:file:size');
       if ($size !== null) {
         $old['file:size'] = phutil_format_bytes($size);
       }
     }
 
     if ($this->hasNewFile()) {
       $file = $this->getNewFile();
       if ($file->getImageWidth()) {
         $dimensions = $file->getImageWidth().'x'.$file->getImageHeight();
         $new['file:dimensions'] = $dimensions;
       }
       $new['file:mimetype'] = $file->getMimeType();
       $new['file:size'] = phutil_format_bytes($file->getByteSize());
     } else {
       $new['file:mimetype'] = idx($metadata, 'new:file:mime-type');
       $size = idx($metadata, 'new:file:size');
       if ($size !== null) {
         $new['file:size'] = phutil_format_bytes($size);
       }
     }
 
     return array($old, $new);
   }
 
   public function renderUndoTemplates() {
     $views = array(
       'l' => id(new PHUIDiffInlineCommentUndoView())->setIsOnRight(false),
       'r' => id(new PHUIDiffInlineCommentUndoView())->setIsOnRight(true),
     );
 
     foreach ($views as $key => $view) {
       $scaffold = $this->getRowScaffoldForInline($view);
       $views[$key] = id(new PHUIDiffInlineCommentTableScaffold())
         ->addRowScaffold($scaffold);
     }
 
     return $views;
   }
 
 
   final protected function getScopeEngine() {
     if (!$this->scopeEngine) {
       $line_map = $this->getNewLineTextMap();
 
       $scope_engine = id(new PhabricatorDiffScopeEngine())
         ->setLineTextMap($line_map);
 
       $this->scopeEngine = $scope_engine;
     }
 
     return $this->scopeEngine;
   }
 
   private function getNewLineTextMap() {
     $new = $this->getNewLines();
 
     $text_map = array();
     foreach ($new as $new_line) {
       if (!isset($new_line['line'])) {
         continue;
       }
       $text_map[$new_line['line']] = $new_line['text'];
     }
 
     return $text_map;
   }
 
 }
diff --git a/src/applications/differential/render/DifferentialChangesetTestRenderer.php b/src/applications/differential/render/DifferentialChangesetTestRenderer.php
index a0d1fad0eb..c7b35d1fb4 100644
--- a/src/applications/differential/render/DifferentialChangesetTestRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTestRenderer.php
@@ -1,143 +1,147 @@
 <?php
 
 abstract class DifferentialChangesetTestRenderer
   extends DifferentialChangesetRenderer {
 
   protected function renderChangeTypeHeader($force) {
     $changeset = $this->getChangeset();
 
     $old = nonempty($changeset->getOldFile(), '-');
     $current = nonempty($changeset->getFilename(), '-');
     $away = nonempty(implode(', ', $changeset->getAwayPaths()), '-');
 
     $ctype = $changeset->getChangeType();
     $ftype = $changeset->getFileType();
     $force = ($force ? '(forced)' : '(unforced)');
 
     return "CTYPE {$ctype} {$ftype} {$force}\n".
            "{$old}\n".
            "{$current}\n".
            "{$away}\n";
   }
 
   protected function renderUndershieldHeader() {
     return null;
   }
 
   public function renderShield($message, $force = 'default') {
     return "SHIELD ({$force}) {$message}\n";
   }
 
   protected function renderPropertyChangeHeader() {
     $changeset = $this->getChangeset();
     list($old, $new) = $this->getChangesetProperties($changeset);
 
     foreach (array_keys($old) as $key) {
       if ($old[$key] === idx($new, $key)) {
         unset($old[$key]);
         unset($new[$key]);
       }
     }
 
     if (!$old && !$new) {
       return null;
     }
 
     $props = '';
     foreach ($old as $key => $value) {
       $props .= "P - {$key} {$value}~\n";
     }
     foreach ($new as $key => $value) {
       $props .= "P + {$key} {$value}~\n";
     }
 
     return "PROPERTIES\n".$props;
   }
 
   public function renderTextChange(
     $range_start,
     $range_len,
     $rows) {
 
     $out = array();
 
     $any_old = false;
     $any_new = false;
     $primitives = $this->buildPrimitives($range_start, $range_len);
     foreach ($primitives as $p) {
       $type = $p['type'];
       switch ($type) {
         case 'old':
         case 'new':
           if ($type == 'old') {
             $any_old = true;
           }
           if ($type == 'new') {
             $any_new = true;
           }
           $num = nonempty($p['line'], '-');
           $render = $p['render'];
           $htype = nonempty($p['htype'], '.');
 
           // TODO: This should probably happen earlier, whenever we deal with
           // \r and \t normalization?
           $render = str_replace(
             array(
               "\r",
               "\n",
             ),
             array(
               '\\r',
               '\\n',
             ),
             $render);
 
           $render = str_replace(
             array(
               '<span class="bright">',
               '</span>',
+              '<span class="depth-out">',
+              '<span class="depth-in">',
             ),
             array(
               '{(',
               ')}',
+              '{<',
+              '{>',
             ),
             $render);
 
           $render = html_entity_decode($render, ENT_QUOTES);
 
           $t = ($type == 'old') ? 'O' : 'N';
 
           $out[] = "{$t} {$num} {$htype} {$render}~";
           break;
         case 'no-context':
           $out[] = 'X <MISSING-CONTEXT>';
           break;
         default:
           $out[] = $type;
           break;
       }
     }
 
     if (!$any_old) {
       $out[] = 'O X <EMPTY>';
     }
 
     if (!$any_new) {
       $out[] = 'N X <EMPTY>';
     }
 
     $out = implode("\n", $out)."\n";
     return $out;
   }
 
 
   public function renderFileChange(
     $old_file = null,
     $new_file = null,
     $id = 0,
     $vs = 0) {
 
     throw new PhutilMethodNotImplementedException();
   }
 
 }
diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
index f40a7f5e0b..b4936201e0 100644
--- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
@@ -1,415 +1,433 @@
 <?php
 
 final class DifferentialChangesetTwoUpRenderer
   extends DifferentialChangesetHTMLRenderer {
 
   private $newOffsetMap;
 
   public function isOneUpRenderer() {
     return false;
   }
 
   protected function getRendererTableClass() {
     return 'diff-2up';
   }
 
   public function getRendererKey() {
     return '2up';
   }
 
   protected function renderColgroup() {
     return phutil_tag('colgroup', array(), array(
       phutil_tag('col', array('class' => 'num')),
       phutil_tag('col', array('class' => 'left')),
       phutil_tag('col', array('class' => 'num')),
       phutil_tag('col', array('class' => 'copy')),
       phutil_tag('col', array('class' => 'right')),
       phutil_tag('col', array('class' => 'cov')),
     ));
   }
 
   public function renderTextChange(
     $range_start,
     $range_len,
     $rows) {
 
     $hunk_starts = $this->getHunkStartLines();
 
     $context_not_available = null;
     if ($hunk_starts) {
       $context_not_available = javelin_tag(
         'tr',
         array(
           'sigil' => 'context-target',
         ),
         phutil_tag(
           'td',
           array(
             'colspan' => 6,
             'class' => 'show-more',
           ),
           pht('Context not available.')));
     }
 
     $html = array();
 
     $old_lines = $this->getOldLines();
     $new_lines = $this->getNewLines();
     $gaps = $this->getGaps();
     $reference = $this->getRenderingReference();
 
     list($left_prefix, $right_prefix) = $this->getLineIDPrefixes();
 
     $changeset = $this->getChangeset();
     $copy_lines = idx($changeset->getMetadata(), 'copy:lines', array());
     $highlight_old = $this->getHighlightOld();
     $highlight_new = $this->getHighlightNew();
     $old_render = $this->getOldRender();
     $new_render = $this->getNewRender();
     $original_left = $this->getOriginalOld();
     $original_right = $this->getOriginalNew();
     $mask = $this->getMask();
 
     $scope_engine = $this->getScopeEngine();
-
     $offset_map = null;
+    $depth_only = $this->getDepthOnlyLines();
 
     for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
       if (empty($mask[$ii])) {
         // If we aren't going to show this line, we've just entered a gap.
         // Pop information about the next gap off the $gaps stack and render
         // an appropriate "Show more context" element. This branch eventually
         // increments $ii by the entire size of the gap and then continues
         // the loop.
         $gap = array_pop($gaps);
         $top = $gap[0];
         $len = $gap[1];
 
         $contents = $this->renderShowContextLinks($top, $len, $rows);
 
         $is_last_block = false;
         if ($ii + $len >= $rows) {
           $is_last_block = true;
         }
 
         $context_text = null;
         $context_line = null;
         if (!$is_last_block) {
           $target_line = $new_lines[$ii + $len]['line'];
           $context_line = $scope_engine->getScopeStart($target_line);
           if ($context_line !== null) {
             // The scope engine returns a line number in the file. We need
             // to map that back to a display offset in the diff.
             if (!$offset_map) {
               $offset_map = $this->getNewLineToOffsetMap();
             }
             $offset = $offset_map[$context_line];
             $context_text = $new_render[$offset];
           }
         }
 
         $container = javelin_tag(
           'tr',
           array(
             'sigil' => 'context-target',
           ),
           array(
             phutil_tag(
               'td',
               array(
                 'colspan' => 2,
                 'class' => 'show-more',
               ),
               $contents),
             phutil_tag(
               'th',
               array(
                 'class' => 'show-context-line',
               ),
               $context_line ? (int)$context_line : null),
             phutil_tag(
               'td',
               array(
                 'colspan' => 3,
                 'class' => 'show-context',
               ),
               // TODO: [HTML] Escaping model here isn't ideal.
               phutil_safe_html($context_text)),
           ));
 
         $html[] = $container;
 
         $ii += ($len - 1);
         continue;
       }
 
       $o_num = null;
       $o_classes = '';
       $o_text = null;
       if (isset($old_lines[$ii])) {
         $o_num  = $old_lines[$ii]['line'];
         $o_text = isset($old_render[$ii]) ? $old_render[$ii] : null;
         if ($old_lines[$ii]['type']) {
           if ($old_lines[$ii]['type'] == '\\') {
             $o_text = $old_lines[$ii]['text'];
             $o_class = 'comment';
           } else if ($original_left && !isset($highlight_old[$o_num])) {
             $o_class = 'old-rebase';
           } else if (empty($new_lines[$ii])) {
             $o_class = 'old old-full';
           } else {
             $o_class = 'old';
           }
           $o_classes = $o_class;
         }
       }
 
       $n_copy = hsprintf('<td class="copy" />');
       $n_cov = null;
       $n_colspan = 2;
       $n_classes = '';
       $n_num  = null;
       $n_text = null;
 
       if (isset($new_lines[$ii])) {
         $n_num  = $new_lines[$ii]['line'];
         $n_text = isset($new_render[$ii]) ? $new_render[$ii] : null;
         $coverage = $this->getCodeCoverage();
 
         if ($coverage !== null) {
           if (empty($coverage[$n_num - 1])) {
             $cov_class = 'N';
           } else {
             $cov_class = $coverage[$n_num - 1];
           }
           $cov_class = 'cov-'.$cov_class;
           $n_cov = phutil_tag('td', array('class' => "cov {$cov_class}"));
           $n_colspan--;
         }
 
         if ($new_lines[$ii]['type']) {
           if ($new_lines[$ii]['type'] == '\\') {
             $n_text = $new_lines[$ii]['text'];
             $n_class = 'comment';
           } else if ($original_right && !isset($highlight_new[$n_num])) {
             $n_class = 'new-rebase';
           } else if (empty($old_lines[$ii])) {
             $n_class = 'new new-full';
           } else {
-            $n_class = 'new';
+
+            // NOTE: At least for the moment, I'm intentionally clearing the
+            // line highlighting only on the right side of the diff when a
+            // line has only depth changes. When a block depth is decreased,
+            // this gives us a large color block on the left (to make it easy
+            // to see the depth change) but a clean diff on the right (to make
+            // it easy to pick out actual code changes).
+
+            if (isset($depth_only[$ii])) {
+              $n_class = '';
+            } else {
+              $n_class = 'new';
+            }
           }
           $n_classes = $n_class;
 
-          if ($new_lines[$ii]['type'] == '\\' || !isset($copy_lines[$n_num])) {
+          $not_copied =
+            // If this line only changed depth, copy markers are pointless.
+            (!isset($copy_lines[$n_num])) ||
+            (isset($depth_only[$ii])) ||
+            ($new_lines[$ii]['type'] == '\\');
+
+          if ($not_copied) {
             $n_copy = phutil_tag('td', array('class' => "copy {$n_class}"));
           } else {
             list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num];
             $title = ($orig_type == '-' ? 'Moved' : 'Copied').' from ';
             if ($orig_file == '') {
               $title .= "line {$orig_line}";
             } else {
               $title .=
                 basename($orig_file).
                 ":{$orig_line} in dir ".
                 dirname('/'.$orig_file);
             }
             $class = ($orig_type == '-' ? 'new-move' : 'new-copy');
             $n_copy = javelin_tag(
               'td',
               array(
                 'meta' => array(
                   'msg' => $title,
                 ),
                 'class' => 'copy '.$class,
               ),
               '');
           }
         }
       }
 
       if (isset($hunk_starts[$o_num])) {
         $html[] = $context_not_available;
       }
 
       if ($o_num && $left_prefix) {
         $o_id = $left_prefix.$o_num;
       } else {
         $o_id = null;
       }
 
       if ($n_num && $right_prefix) {
         $n_id = $right_prefix.$n_num;
       } else {
         $n_id = null;
       }
 
       $old_comments = $this->getOldComments();
       $new_comments = $this->getNewComments();
       $scaffolds = array();
 
       if ($o_num && isset($old_comments[$o_num])) {
         foreach ($old_comments[$o_num] as $comment) {
           $inline = $this->buildInlineComment(
             $comment,
             $on_right = false);
           $scaffold = $this->getRowScaffoldForInline($inline);
 
           if ($n_num && isset($new_comments[$n_num])) {
             foreach ($new_comments[$n_num] as $key => $new_comment) {
               if ($comment->isCompatible($new_comment)) {
                 $companion = $this->buildInlineComment(
                   $new_comment,
                   $on_right = true);
 
                 $scaffold->addInlineView($companion);
                 unset($new_comments[$n_num][$key]);
                 break;
               }
             }
           }
 
 
           $scaffolds[] = $scaffold;
         }
       }
 
       if ($n_num && isset($new_comments[$n_num])) {
         foreach ($new_comments[$n_num] as $comment) {
           $inline = $this->buildInlineComment(
             $comment,
             $on_right = true);
 
           $scaffolds[] = $this->getRowScaffoldForInline($inline);
         }
       }
 
       // NOTE: This is a unicode zero-width space, which we use as a hint when
       // intercepting 'copy' events to make sure sensible text ends up on the
       // clipboard. See the 'phabricator-oncopy' behavior.
       $zero_space = "\xE2\x80\x8B";
 
       $html[] = phutil_tag('tr', array(), array(
         phutil_tag('th', array('id' => $o_id, 'class' => $o_classes), $o_num),
         phutil_tag('td', array('class' => $o_classes), $o_text),
         phutil_tag('th', array('id' => $n_id, 'class' => $n_classes), $n_num),
         $n_copy,
         phutil_tag(
           'td',
           array('class' => $n_classes, 'colspan' => $n_colspan),
           array(
             phutil_tag('span', array('class' => 'zwsp'), $zero_space),
             $n_text,
           )),
         $n_cov,
       ));
 
       if ($context_not_available && ($ii == $rows - 1)) {
         $html[] = $context_not_available;
       }
 
       foreach ($scaffolds as $scaffold) {
         $html[] = $scaffold;
       }
     }
 
     return $this->wrapChangeInTable(phutil_implode_html('', $html));
   }
 
   public function renderFileChange(
     $old_file = null,
     $new_file = null,
     $id = 0,
     $vs = 0) {
 
     $old = null;
     if ($old_file) {
       $old = $this->renderImageStage($old_file);
     }
 
     $new = null;
     if ($new_file) {
       $new = $this->renderImageStage($new_file);
     }
 
     // If we don't have an explicit "vs" changeset, it's the left side of the
     // "id" changeset.
     if (!$vs) {
       $vs = $id;
     }
 
     $html_old = array();
     $html_new = array();
     foreach ($this->getOldComments() as $on_line => $comment_group) {
       foreach ($comment_group as $comment) {
         $inline = $this->buildInlineComment(
           $comment,
           $on_right = false);
         $html_old[] = $this->getRowScaffoldForInline($inline);
       }
     }
     foreach ($this->getNewComments() as $lin_line => $comment_group) {
       foreach ($comment_group as $comment) {
         $inline = $this->buildInlineComment(
           $comment,
           $on_right = true);
         $html_new[] = $this->getRowScaffoldForInline($inline);
       }
     }
 
     if (!$old) {
       $th_old = phutil_tag('th', array());
     } else {
       $th_old = phutil_tag('th', array('id' => "C{$vs}OL1"), 1);
     }
 
     if (!$new) {
       $th_new = phutil_tag('th', array());
     } else {
       $th_new = phutil_tag('th', array('id' => "C{$id}NL1"), 1);
     }
 
     $output = hsprintf(
       '<tr class="differential-image-diff">'.
         '%s'.
         '<td class="differential-old-image">%s</td>'.
         '%s'.
         '<td class="differential-new-image" colspan="3">%s</td>'.
       '</tr>'.
       '%s'.
       '%s',
       $th_old,
       $old,
       $th_new,
       $new,
       phutil_implode_html('', $html_old),
       phutil_implode_html('', $html_new));
 
     $output = $this->wrapChangeInTable($output);
 
     return $this->renderChangesetTable($output);
   }
 
   public function getRowScaffoldForInline(PHUIDiffInlineCommentView $view) {
     return id(new PHUIDiffTwoUpInlineCommentRowScaffold())
       ->addInlineView($view);
   }
 
   private function getNewLineToOffsetMap() {
     if ($this->newOffsetMap === null) {
       $new = $this->getNewLines();
 
       $map = array();
       foreach ($new as $offset => $new_line) {
         if ($new_line['line'] === null) {
           continue;
         }
         $map[$new_line['line']] = $offset;
       }
 
       $this->newOffsetMap = $map;
     }
 
     return $this->newOffsetMap;
   }
 
 }
diff --git a/webroot/rsrc/css/application/differential/changeset-view.css b/webroot/rsrc/css/application/differential/changeset-view.css
index b9683dc7c6..2cfa753a42 100644
--- a/webroot/rsrc/css/application/differential/changeset-view.css
+++ b/webroot/rsrc/css/application/differential/changeset-view.css
@@ -1,409 +1,430 @@
 /**
  * @provides differential-changeset-view-css
  * @requires phui-inline-comment-view-css
  */
 
 .differential-changeset {
   position: relative;
   margin: 0;
   padding-top: 16px;
   overflow-x: auto;
 
   /* Fixes what seems to be a layout bug in Firefox which causes scrollbars,
      to appear unpredictably, see discussion in T7690. */
   overflow-y: hidden;
 }
 
 .device-phone .differential-changeset {
   overflow-x: scroll;
   -webkit-overflow-scrolling: touch;
 }
 
 .differential-diff {
   background: {$diff.background};
   width: 100%;
   border-top: 1px solid {$lightblueborder};
   border-bottom: 1px solid {$lightblueborder};
   table-layout: fixed;
 }
 
 .differential-diff.diff-2up {
   min-width: 780px;
 }
 
 .differential-diff col.num {
   width: 45px;
 }
 
 .device .differential-diff.diff-1up col.num {
   width: 32px;
 }
 
 .differential-diff.diff-2up col.left,
 .differential-diff.diff-2up col.right {
   width: 49.25%;
 }
 
 .differential-diff.diff-1up col.unified {
   width: 99.5%;
 }
 
 .differential-diff col.copy {
   width: 0.5%;
 }
 
 .differential-diff col.cov {
   width: 1%;
 }
 
 .differential-diff td {
   vertical-align: top;
   white-space: pre-wrap;
   word-wrap: break-word;
   padding: 1px 8px;
 }
 
 .device .differential-diff td {
   padding: 1px 4px;
 }
 
 .differential-diff td .zwsp {
   position: absolute;
   width: 0;
 }
 
 .differential-diff th {
   text-align: right;
   padding: 1px 6px 1px 0;
   vertical-align: top;
   background: {$lightbluebackground};
   color: {$bluetext};
   cursor: pointer;
   border-right: 1px solid {$thinblueborder};
   overflow: hidden;
 
   -moz-user-select: -moz-none;
   -khtml-user-select: none;
   -webkit-user-select: none;
   -ms-user-select: none;
   user-select: none;
 }
 
 .prose-diff {
   padding: 12px 0;
   white-space: pre-wrap;
   color: {$greytext};
 }
 
 .prose-diff-frame {
   padding: 12px;
 }
 
 .prose-diff span.old,
 .prose-diff span.new {
   padding: 0 2px;
 }
 
 .prose-diff span.old,
 .prose-diff span.new {
   color: {$darkgreytext};
 }
 
 .differential-changeset-immutable .differential-diff th {
   cursor: auto;
 }
 
 .differential-diff td.old {
   background: {$old-background};
 }
 
 .differential-diff td.new {
   background: {$new-background};
 }
 
 .differential-diff td.old-rebase {
   background: #ffeeee;
 }
 
 .differential-diff td.new-rebase {
   background: #eeffee;
 }
 
 .differential-diff td.old span.bright,
 .differential-diff td.old-full,
 .prose-diff span.old {
   background: {$old-bright};
 }
 
+
 .differential-diff td.new span.bright,
 .differential-diff td.new-full,
 .prose-diff span.new {
   background: {$new-bright};
 }
 
+.differential-diff td span.depth-out,
+.differential-diff td span.depth-in {
+  padding: 2px 0;
+  background-size: 12px 12px;
+  background-repeat: no-repeat;
+  background-position: left center;
+}
+
+.differential-diff td span.depth-out {
+  background-image: url(/rsrc/image/chevron-out.png);
+  background-color: {$old-background-strong};
+}
+
+.differential-diff td span.depth-in {
+  background-position: 2px center;
+  background-image: url(/rsrc/image/chevron-in.png);
+  background-color: {$new-background-strong};
+}
+
+
 .differential-diff td.copy {
   min-width: 0.5%;
   width: 0.5%;
   padding: 0;
 }
 
 .differential-diff td.new-copy,
 .differential-diff td.new-copy span.bright {
   background: {$copy-background};
 }
 
 .differential-diff td.new-move,
 .differential-diff td.new-move span.bright {
   background: {$move-background};
 }
 
 .differential-diff td.comment {
   background: #dddddd;
 }
 
 .differential-diff td.cov {
   padding: 0;
 }
 
 td.cov-U {
   background: #dd8866;
 }
 
 td.cov-C {
   background: #66bbff;
 }
 
 td.cov-N {
   background: #ddeeff;
 }
 
 td.cov-X {
   background: #aa00aa;
 }
 
 td.cov-I {
   background: {$lightgreybackground};
 }
 
 .differential-diff td.source-cov-C,
 .differential-diff td.source-cov-C span.bright {
   background: #cceeff;
 }
 
 .differential-diff td.source-cov-U,
 .differential-diff td.source-cov-U span.bright {
   background: #ffbb99;
 }
 
 .differential-diff td.source-cov-N,
 .differential-diff td.source-cov-N span.bright {
   background: #f3f6ff;
 }
 
 .differential-diff td.show-more,
 .differential-diff th.show-context-line,
 .differential-diff td.show-context,
 .differential-diff td.differential-shield {
   background: {$lightbluebackground};
   padding: 12px 0;
   border-top: 1px solid {$thinblueborder};
   border-bottom: 1px solid {$thinblueborder};
 }
 
 .device .differential-diff td.show-more,
 .device .differential-diff th.show-context-line,
 .device .differential-diff td.show-context,
 .device .differential-diff td.differential-shield {
   padding: 6px 0;
 }
 
 .differential-diff td.show-more,
 .differential-diff td.differential-shield {
   font: {$basefont};
   font-size: {$smallerfontsize};
   white-space: normal;
 }
 
 .differential-diff td.show-more {
   text-align: center;
   color: {$bluetext};
 }
 
 .differential-diff th.show-context-line {
   padding-right: 6px;
 }
 
 .differential-diff td.show-context {
   padding-left: 14px;
 }
 
 .differential-diff td.differential-shield {
   text-align: center;
 }
 
 .differential-diff td.differential-shield a {
   font-weight: bold;
 }
 
 .differential-diff .differential-image-diff {
   background-image: url(/rsrc/image/checker_light.png);
 }
 
 .differential-diff .differential-image-diff:hover {
   background-image: url(/rsrc/image/checker_dark.png);
 }
 
 .differential-diff .differential-image-diff td {
   padding: 8px;
 }
 
 .differential-image-stage {
   overflow: auto;
 }
 
 .differential-meta-notice {
   border-top: 1px solid {$gentle.highlight.border};
   border-bottom: 1px solid {$gentle.highlight.border};
   background-color: {$gentle.highlight};
   padding: 12px;
 }
 
 .differential-meta-notice + .differential-diff {
   border-top: none;
 }
 
 .differential-changeset h1 {
   font-size: {$biggestfontsize};
   padding: 2px 0 20px 12px;
   line-height: 20px;
   color: {$blacktext};
 }
 
 .device-phone .differential-changeset h1 {
   word-break: break-word;
   margin-right: 8px;
 }
 
 .differential-reticle {
   background-color: {$sh-yellowbackground};
   border: 1px solid {$sh-yellowborder};
   position: absolute;
   opacity: 0.5;
   top: 0;
   left: 0;
   box-sizing: border-box;
   pointer-events: none;
 }
 
 .differential-diff .inline > td {
   padding: 0;
 }
 
 .differential-loading {
   border-top: 1px solid {$gentle.highlight.border};
   border-bottom: 1px solid {$gentle.highlight.border};
   background-color: {$gentle.highlight};
   padding: 12px;
   text-align: center;
 }
 
 .differential-collapse-undo {
   color: {$darkbluetext};
   padding: 12px;
   border: 1px solid {$blue};
   text-align: center;
   background-color: {$lightblue};
   margin: 8px;
 }
 
 .differential-collapse-undo a {
   font-weight: bold;
 }
 
 .differential-file-icon-header .phui-icon-view {
   display: inline-block;
   margin: 0 6px 2px 0;
   vertical-align: middle;
   font-size: 14px;
 }
 
 .device-phone .differential-file-icon-header .phui-icon-view {
   display: none;
 }
 
 .differential-changeset-buttons {
   float: right;
   margin-right: 12px;
 }
 
 .device-phone .differential-changeset-buttons .button .phui-button-text {
   visibility: hidden;
   width: 0;
   margin-left: 8px;
 }
 
 .differential-property-table {
   margin: 12px;
   background: {$lightgreybackground};
   border: 1px solid {$lightblueborder};
   border-bottom: 1px solid {$blueborder};
 }
 
 .differential-property-table td em {
   color: {$lightgreytext};
 }
 
 .differential-property-table td.oval {
   background: #ffd0d0;
   width: 50%;
 }
 
 .differential-property-table td.nval {
   background: #d0ffd0;
   width: 50%;
 }
 
 tr.differential-inline-hidden {
   display: none;
 }
 
 tr.differential-inline-loading {
   opacity: 0.5;
 }
 
 .differential-review-stage {
   position: relative;
 }
 
 .diff-banner {
   position: fixed;
   top: 0;
   left: 0;
   right: 0;
   background: {$page.content};
   box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
   border-bottom: 1px solid {$lightgreyborder};
   padding: 8px 18px;
   vertical-align: middle;
   font-weight: bold;
   font-size: {$biggerfontsize};
   line-height: 28px;
 }
 
 .diff-banner-path {
   color: {$greytext};
 }
 
 .diff-banner-buttons .button {
   margin-left: 8px;
 }
 
 .diff-banner-has-unsaved,
 .diff-banner-has-unsubmitted,
 .diff-banner-has-draft-done {
   background: {$gentle.highlight};
 }
 
 .diff-banner-buttons {
   float: right;
 }
diff --git a/webroot/rsrc/image/chevron-in.png b/webroot/rsrc/image/chevron-in.png
new file mode 100644
index 0000000000..373d39cfe1
Binary files /dev/null and b/webroot/rsrc/image/chevron-in.png differ
diff --git a/webroot/rsrc/image/chevron-out.png b/webroot/rsrc/image/chevron-out.png
new file mode 100644
index 0000000000..787772eb27
Binary files /dev/null and b/webroot/rsrc/image/chevron-out.png differ