diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -10755,10 +10755,7 @@
     'PhrictionChangeType' => 'PhrictionConstants',
     'PhrictionConduitAPIMethod' => 'ConduitAPIMethod',
     'PhrictionConstants' => 'Phobject',
-    'PhrictionContent' => array(
-      'PhrictionDAO',
-      'PhabricatorMarkupInterface',
-    ),
+    'PhrictionContent' => 'PhrictionDAO',
     'PhrictionContentPHIDType' => 'PhabricatorPHIDType',
     'PhrictionContentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'PhrictionController' => 'PhabricatorController',
diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php
--- a/src/applications/phriction/controller/PhrictionDocumentController.php
+++ b/src/applications/phriction/controller/PhrictionDocumentController.php
@@ -97,8 +97,12 @@
       if ($current_status == PhrictionChangeType::CHANGE_EDIT ||
         $current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
 
-        $core_content = $content->renderContent($viewer);
-        $toc = $this->getToc($content);
+        $remarkup_view = $content->newRemarkupView($viewer);
+
+        $core_content = $remarkup_view->render();
+
+        $toc = $remarkup_view->getTableOfContents();
+        $toc = $this->getToc($toc);
 
       } else if ($current_status == PhrictionChangeType::CHANGE_DELETE) {
         $notice = new PHUIInfoView();
@@ -474,8 +478,8 @@
     return $this->slug;
   }
 
-  protected function getToc(PhrictionContent $content) {
-    $toc = $content->getRenderedTableOfContents();
+  protected function getToc($toc) {
+
     if ($toc) {
       $toc = phutil_tag_div('phui-document-toc-content', array(
         phutil_tag_div(
@@ -484,6 +488,7 @@
         $toc,
       ));
     }
+
     return $toc;
   }
 
diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php
--- a/src/applications/phriction/storage/PhrictionContent.php
+++ b/src/applications/phriction/storage/PhrictionContent.php
@@ -1,13 +1,7 @@
 <?php
 
-/**
- * @task markup Markup Interface
- */
 final class PhrictionContent
-  extends PhrictionDAO
-  implements PhabricatorMarkupInterface {
-
-  const MARKUP_FIELD_BODY = 'markup:body';
+  extends PhrictionDAO {
 
   protected $id;
   protected $documentID;
@@ -22,16 +16,6 @@
   protected $changeType;
   protected $changeRef;
 
-  private $renderedTableOfContents;
-
-  public function renderContent(PhabricatorUser $viewer) {
-    return PhabricatorMarkupEngine::renderOneObject(
-      $this,
-      self::MARKUP_FIELD_BODY,
-      $viewer,
-      $this);
-  }
-
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
@@ -66,68 +50,10 @@
     return PhrictionContentPHIDType::TYPECONST;
   }
 
-
-/* -(  Markup Interface  )--------------------------------------------------- */
-
-
-  /**
-   * @task markup
-   */
-  public function getMarkupFieldKey($field) {
-    $content = $this->getMarkupText($field);
-    return PhabricatorMarkupEngine::digestRemarkupContent($this, $content);
-  }
-
-
-  /**
-   * @task markup
-   */
-  public function getMarkupText($field) {
-    return $this->getContent();
-  }
-
-
-  /**
-   * @task markup
-   */
-  public function newMarkupEngine($field) {
-    return PhabricatorMarkupEngine::newPhrictionMarkupEngine();
+  public function newRemarkupView(PhabricatorUser $viewer) {
+    return id(new PHUIRemarkupView($viewer, $this->getContent()))
+      ->setRemarkupOption(PHUIRemarkupView::OPTION_GENERATE_TOC, true)
+      ->setGenerateTableOfContents(true);
   }
 
-
-  /**
-   * @task markup
-   */
-  public function didMarkupText(
-    $field,
-    $output,
-    PhutilMarkupEngine $engine) {
-
-    $this->renderedTableOfContents =
-      PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine);
-
-    return phutil_tag(
-      'div',
-      array(
-        'class' => 'phabricator-remarkup',
-      ),
-      $output);
-  }
-
-  /**
-   * @task markup
-   */
-  public function getRenderedTableOfContents() {
-    return $this->renderedTableOfContents;
-  }
-
-
-  /**
-   * @task markup
-   */
-  public function shouldUseMarkupCache($field) {
-    return (bool)$this->getID();
-  }
-
-
 }
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
@@ -12,6 +12,10 @@
   private $engineRuleset;
   private $engine;
   private $disableCache;
+  private $contentCacheFragment;
+
+  private $generateTableOfContents;
+  private $tableOfContents;
 
   public function setEngineRuleset($engine_ruleset) {
     $this->engineRuleset = $engine_ruleset;
@@ -54,7 +58,34 @@
     return $this->disableCache;
   }
 
+  public function setGenerateTableOfContents($generate) {
+    $this->generateTableOfContents = $generate;
+    return $this;
+  }
+
+  public function getGenerateTableOfContents() {
+    return $this->generateTableOfContents;
+  }
+
+  public function getTableOfContents() {
+    return $this->tableOfContents;
+  }
+
+  public function setContentCacheFragment($fragment) {
+    $this->contentCacheFragment = $fragment;
+    return $this;
+  }
+
+  public function getContentCacheFragment() {
+    return $this->contentCacheFragment;
+  }
+
   public function getMarkupFieldKey($field) {
+    $fragment = $this->getContentCacheFragment();
+    if ($fragment !== null) {
+      return $fragment;
+    }
+
     return PhabricatorHash::digestForIndex($this->getContent()).':oneoff';
   }
 
@@ -81,7 +112,13 @@
     $output,
     PhutilMarkupEngine $engine) {
 
+    if ($this->getGenerateTableOfContents()) {
+      $toc = PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine);
+      $this->tableOfContents = $toc;
+    }
+
     require_celerity_resource('phabricator-remarkup-css');
+
     return phutil_tag(
       'div',
       array(
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
@@ -14,11 +14,14 @@
   private $corpus;
   private $contextObject;
   private $options;
+  private $oneoff;
+  private $generateTableOfContents;
 
   // TODO: In the long run, rules themselves should define available options.
   // For now, just define constants here so we can more easily replace things
   // later once this is cleaned up.
   const OPTION_PRESERVE_LINEBREAKS = 'preserve-linebreaks';
+  const OPTION_GENERATE_TOC = 'header.generate-toc';
 
   public function __construct(PhabricatorUser $viewer, $corpus) {
     $this->setUser($viewer);
@@ -46,6 +49,19 @@
     return $this;
   }
 
+  public function setGenerateTableOfContents($generate) {
+    $this->generateTableOfContents = $generate;
+    return $this;
+  }
+
+  public function getGenerateTableOfContents() {
+    return $this->generateTableOfContents;
+  }
+
+  public function getTableOfContents() {
+    return $this->oneoff->getTableOfContents();
+  }
+
   public function render() {
     $viewer = $this->getViewer();
     $corpus = $this->corpus;
@@ -54,7 +70,8 @@
     $options = $this->options;
 
     $oneoff = id(new PhabricatorMarkupOneOff())
-      ->setContent($corpus);
+      ->setContent($corpus)
+      ->setContentCacheFragment($this->getContentCacheFragment());
 
     if ($options) {
       $oneoff->setEngine($this->getEngine());
@@ -62,6 +79,10 @@
       $oneoff->setPreserveLinebreaks(true);
     }
 
+    $generate_toc = $this->getGenerateTableOfContents();
+    $oneoff->setGenerateTableOfContents($generate_toc);
+    $this->oneoff = $oneoff;
+
     $content = PhabricatorMarkupEngine::renderOneObject(
       $oneoff,
       'default',
@@ -76,10 +97,7 @@
     $viewer = $this->getViewer();
 
     $viewer_key = $viewer->getCacheFragment();
-
-    ksort($options);
-    $engine_key = serialize($options);
-    $engine_key = PhabricatorHash::digestForIndex($engine_key);
+    $engine_key = $this->getEngineCacheFragment();
 
     $cache = PhabricatorCaches::getRequestCache();
     $cache_key = "remarkup.engine({$viewer_key}, {$engine_key})";
@@ -93,4 +111,28 @@
     return $engine;
   }
 
+  private function getEngineCacheFragment() {
+    $options = $this->options;
+
+    ksort($options);
+
+    $engine_key = serialize($options);
+    $engine_key = PhabricatorHash::digestForIndex($engine_key);
+
+    return $engine_key;
+  }
+
+  private function getContentCacheFragment() {
+    $corpus = $this->corpus;
+
+    $content_fragment = PhabricatorHash::digestForIndex($corpus);
+    $options_fragment = array(
+      'toc' => $this->getGenerateTableOfContents(),
+    );
+    $options_fragment = serialize($options_fragment);
+    $options_fragment = PhabricatorHash::digestForIndex($options_fragment);
+
+    return "remarkup({$content_fragment}, {$options_fragment})";
+  }
+
 }