diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -72,7 +72,7 @@
     'rsrc/css/application/differential/table-of-contents.css' => 'bba788b9',
     '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-repository.css' => '6c7a1d8c',
     'rsrc/css/application/diffusion/diffusion.css' => 'e46232d6',
     'rsrc/css/application/feed/feed.css' => 'd8b6e3f8',
     'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4',
@@ -471,6 +471,7 @@
     '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-collapsiblejx.js' => '7072263b',
     'rsrc/js/core/behavior-copy.js' => 'cf32921f',
     'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94',
     'rsrc/js/core/behavior-device.js' => 'ac2b1e01',
@@ -570,7 +571,7 @@
     'diffusion-css' => 'e46232d6',
     'diffusion-icons-css' => '23b31a1b',
     'diffusion-readme-css' => 'b68a76e4',
-    'diffusion-repository-css' => 'b89e8c6c',
+    'diffusion-repository-css' => '6c7a1d8c',
     'diviner-shared-css' => '4bd263b0',
     'font-fontawesome' => '3883938a',
     'font-lato' => '23631304',
@@ -600,6 +601,7 @@
     'javelin-behavior-bulk-job-reload' => '3829a3cf',
     'javelin-behavior-calendar-month-view' => '158c64e0',
     'javelin-behavior-choose-control' => '04f8a1e3',
+    'javelin-behavior-collapsiblejx' => '7072263b',
     'javelin-behavior-comment-actions' => '4dffaeb2',
     'javelin-behavior-config-reorder-fields' => '2539f834',
     'javelin-behavior-conpherence-menu' => '8c2ed2bf',
@@ -1587,6 +1589,10 @@
       'javelin-workflow',
       'javelin-dom',
     ),
+    '7072263b' => array(
+      'javelin-behavior',
+      'javelin-dom',
+    ),
     '727a5a61' => array(
       'phuix-icon-view',
     ),
diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php
--- a/src/applications/diffusion/controller/DiffusionBrowseController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseController.php
@@ -190,6 +190,10 @@
           ->setRequest($request)
           ->setDiffusionRequest($drequest);
 
+        if ($engine instanceof PHUIRemarkupView) {
+          $engine->setGenerateTableOfContents(true);
+        }
+
         $corpus = $engine->newDocumentView($ref);
 
         $this->corpusButtons[] = $this->renderFileButton();
diff --git a/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php b/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php
--- a/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorRemarkupDocumentEngine.php
@@ -36,6 +36,7 @@
     $content = phutil_utf8ize($content);
 
     $remarkup = new PHUIRemarkupView($viewer, $content);
+    $remarkup->setGenerateTableOfContents(true);
 
     $container = phutil_tag(
       'div',
diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php
--- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php
+++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php
@@ -208,12 +208,12 @@
     $this->activeEngine = $engine;
 
     $encode_setting = $request->getStr('encode');
-    if (strlen($encode_setting)) {
+    if (phutil_nonempty_string($encode_setting)) {
       $engine->setEncodingConfiguration($encode_setting);
     }
 
     $highlight_setting = $request->getStr('highlight');
-    if (strlen($highlight_setting)) {
+    if (phutil_nonempty_string($highlight_setting)) {
       $engine->setHighlightingConfiguration($highlight_setting);
     }
 
diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php
--- a/src/infrastructure/markup/PhabricatorMarkupEngine.php
+++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php
@@ -459,6 +459,10 @@
         $engine = self::newMarkupEngine(array());
         $engine->setConfig('pygments.enabled', false);
         break;
+      case 'remarkupField':
+        $engine = self::newMarkupEngine(array());
+        $engine->setConfig('header.generate-toc', true);
+        break;
       default:
         throw new Exception(pht('Unknown engine ruleset: %s!', $ruleset));
     }
diff --git a/src/infrastructure/markup/PhabricatorMarkupOneOff.php b/src/infrastructure/markup/PhabricatorMarkupOneOff.php
--- a/src/infrastructure/markup/PhabricatorMarkupOneOff.php
+++ b/src/infrastructure/markup/PhabricatorMarkupOneOff.php
@@ -97,7 +97,11 @@
     if ($this->engineRuleset) {
       return PhabricatorMarkupEngine::getEngine($this->engineRuleset);
     } else if ($this->preserveLinebreaks) {
-      return PhabricatorMarkupEngine::getEngine();
+      $engine = PhabricatorMarkupEngine::getEngine();
+      if ($this->generateTableOfContents) {
+        $engine->setConfig('header.generate-toc', true);
+      }
+      return $engine;
     } else {
       return PhabricatorMarkupEngine::getEngine('nolinebreaks');
     }
@@ -115,10 +119,11 @@
     if ($this->getGenerateTableOfContents()) {
       $toc = PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine);
       $this->tableOfContents = $toc;
+      $collapsible_index = $this->buildCollapsibleIndex($toc);
+      $output = phutil_implode_html("\n", array($collapsible_index, $output));
     }
 
     require_celerity_resource('phabricator-remarkup-css');
-
     return phutil_tag(
       'div',
       array(
@@ -127,6 +132,57 @@
       $output);
   }
 
+  /**
+   * Build a collapsible table of contents display consisting of a
+   * screen-wide button to toggle the display, and the table of
+   * contents itself.
+   * @param PhutilSafeHTML $tocThe table of contents HTML.
+   * @return PhutilSafeHTML Div containing a button and the toc.
+   */
+  private function buildCollapsibleIndex($toc) {
+    $config = array(
+      'container_id' => celerity_generate_unique_node_id(),
+      'toggle_button_id' => celerity_generate_unique_node_id(),
+      'button_icon' => array(
+        'id' => celerity_generate_unique_node_id(),
+        'open_class' => 'fa-folder',
+        'close_class' => 'fa-folder-open',
+      ),
+      'button_i18n' => array(
+        'id' => celerity_generate_unique_node_id(),
+        'open_text' => pht('Open Index'),
+        'close_text' => pht('Close Index'),
+      ),
+    );
+
+    $button_icon = id(new PHUIIconView())
+      ->setID($config['button_icon']['id'])
+      ->setIcon($config['button_icon']['open_class']);
+
+    $button_text_span = phutil_tag(
+      'span',
+      array('class' => 'collapsible', 'id' => $config['button_i18n']['id']),
+      $config['button_i18n']['open_text']);
+
+    $button = phutil_tag(
+      'button',
+      array(
+        'type' => 'button',
+        'class' => 'collapsible',
+        'id' => $config['toggle_button_id'],
+      ),
+      phutil_implode_html("\n", array($button_icon, $button_text_span)));
+
+    $toc_div = phutil_tag(
+      'div',
+      array('class' => 'collapsible-content', 'id' => $config['container_id']),
+      $toc);
+
+    require_celerity_resource('font-fontawesome');
+    Javelin::initBehavior('collapsiblejx', $config);
+    return phutil_implode_html("\n", array($button, $toc_div));
+  }
+
   public function shouldUseMarkupCache($field) {
     if ($this->getDisableCache()) {
       return false;
diff --git a/src/infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php b/src/infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php
--- a/src/infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php
+++ b/src/infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php
@@ -134,7 +134,7 @@
       list($level, $name) = $info;
 
       while ($depth < $level) {
-        $toc[] = hsprintf('<ul>');
+        $toc[] = hsprintf('<ul class="remarkup-list">');
         $depth++;
       }
       while ($depth > $level) {
diff --git a/src/infrastructure/markup/remarkup/__tests__/remarkup/interpreter-test.txt b/src/infrastructure/markup/remarkup/__tests__/remarkup/interpreter-test.txt
--- a/src/infrastructure/markup/remarkup/__tests__/remarkup/interpreter-test.txt
+++ b/src/infrastructure/markup/remarkup/__tests__/remarkup/interpreter-test.txt
@@ -30,9 +30,7 @@
 Content: (content)
 Argv: (x=y)
 
-
-
-<div class="remarkup-interpreter-error">No interpreter found: phutil_fake_test_block_interpreter</div>
+<p>phutil_fake_test_block_interpreter {{{ content }}}</p>
 ~~~~~~~~~~
 Content: (content)
 Argv: (foo=bar)
@@ -53,6 +51,4 @@
 Content: (content)
 Argv: (x=y)
 
-
-
-(No interpreter found: phutil_fake_test_block_interpreter)
+phutil_fake_test_block_interpreter {{{ content }}}
diff --git a/src/infrastructure/markup/view/PHUIRemarkupView.php b/src/infrastructure/markup/view/PHUIRemarkupView.php
--- a/src/infrastructure/markup/view/PHUIRemarkupView.php
+++ b/src/infrastructure/markup/view/PHUIRemarkupView.php
@@ -85,7 +85,7 @@
 
     $content = PhabricatorMarkupEngine::renderOneObject(
       $oneoff,
-      'default',
+      'remarkupField',
       $viewer,
       $context);
 
diff --git a/webroot/rsrc/css/application/diffusion/diffusion-repository.css b/webroot/rsrc/css/application/diffusion/diffusion-repository.css
--- a/webroot/rsrc/css/application/diffusion/diffusion-repository.css
+++ b/webroot/rsrc/css/application/diffusion/diffusion-repository.css
@@ -11,3 +11,40 @@
   .diffusion-panel-header-view.phui-header-shell {
     padding: 8px 4px 8px 16px;
 }
+
+button.collapsible {
+  background-color: {$blue.button.color};
+  border: none;
+  color: #FFFFFF;
+  cursor: pointer;
+  font-size: {$biggerfontsize};
+  outline: none;
+  padding: 18px;
+  text-align: left;
+  width: 100%;
+}
+
+button.collapsible:hover {
+  background-color: {$blue.button.hover};
+}
+
+.collapsible-content {
+  background-color: {$lightgreybackground};
+  display: none;
+  overflow: hidden;
+  padding: 0 18px;
+}
+
+/* The "first element" in a Remarkup page receives a "margin-top: 0".
+   This is normally OK, but ugly for the TOC, so we want to replace it.
+   That "first element" selector is very specific - to override it
+   we have to set important.
+   The "margin-bottom" is copied since the inherited value may change. */
+.collapsible-content > ul.remarkup-list {
+  margin-top: 12px !important;
+  margin-bottom: 12px;
+}
+
+.collapsible-content.collapsible-content-open {
+  display: block;
+}
diff --git a/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner b/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
--- a/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
+++ b/webroot/rsrc/externals/javelin/docs/concepts/behaviors.diviner
@@ -38,7 +38,7 @@
 
   lang=php
   $config = array('hogName' => 'Ethel');
-  JavelinHelper::initBehaviors('win-a-hog', $config);
+  Javelin::initBehavior('win-a-hog', $config);
 
 Regardless, this will alert the user that they've won a hog (named Ethel, which
 is a good name for a hog) when they load the page.
diff --git a/webroot/rsrc/js/core/behavior-collapsiblejx.js b/webroot/rsrc/js/core/behavior-collapsiblejx.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/core/behavior-collapsiblejx.js
@@ -0,0 +1,54 @@
+/**
+ * @provides javelin-behavior-collapsiblejx
+ * @requires javelin-behavior
+ *           javelin-dom
+ *
+ * Given a collapsible div and a button to open and close it, call with:
+ *
+ *   Javelin::initBehavior(
+ *      'collapsiblejx',
+ *      array(
+ *        'container_id' => $theCollapsibleDivId,
+ *        'toggle_button_id' => $theButtonId,
+ *        'button_icon' => array(
+ *          'id' => $theButtonIconId,
+ *          'open_class' => 'fa-folder',
+ *          'close_class' => 'fa-folder-open',
+ *        ),
+ *        'button_i18n' => array(
+ *          'id' => $theButtonTextId,
+ *          'open_text' => pht('Open Whatever This Is'),
+ *          'close_text' => pht('Close Whatever This Is'),
+ *        ),
+ *      )
+ *    );
+ *
+ * button_icon and button_i18n are both optional. If you want either or
+ * none, that's fine.
+ *
+ */
+
+JX.behavior('collapsiblejx', function(config, statics) {
+  var open = false;
+  JX.Stratcom.listen(
+    'click',
+    'id:' + config.toggle_button_id,
+    function(e) {
+      var containerNode = JX.$(config.container_id);
+      open = !open;
+      JX.DOM.alterClass(containerNode, 'collapsible-content-open', open);
+
+      // If element is open, we want a close button, and vice-versa.
+     if ('button_icon' in config) {
+        var button = JX.$(config.button_icon.id);
+        JX.DOM.alterClass(button, config.button_icon.open_class, !open);
+        JX.DOM.alterClass(button, config.button_icon.close_class, open);
+      }
+
+      if ('button_i18n' in config) {
+        var btn_text = config.button_i18n[open ? 'close_text' : 'open_text'];
+        JX.$(config.button_i18n.id).innerText = btn_text;
+      }
+    }
+  );
+});