diff --git a/src/applications/pholio/controller/PholioImageHistoryController.php b/src/applications/pholio/controller/PholioImageHistoryController.php
index 186a930057..7b6d17480f 100644
--- a/src/applications/pholio/controller/PholioImageHistoryController.php
+++ b/src/applications/pholio/controller/PholioImageHistoryController.php
@@ -1,96 +1,96 @@
 <?php
 
 /**
  * @group pholio
  */
 final class PholioImageHistoryController extends PholioController {
 
   private $imageID;
 
   public function willProcessRequest(array $data) {
     $this->imageID = $data['id'];
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $image = id(new PholioImageQuery())
       ->setViewer($user)
       ->withIDs(array($this->imageID))
       ->executeOne();
 
     if (!$image) {
       return new Aphront404Response();
     }
 
     // note while we have a mock object, its missing images we need to show
     // the history of what's happened here.
     // fetch the real deal
-    //
+
     $mock = id(new PholioMockQuery())
       ->setViewer($user)
       ->needImages(true)
       ->withIDs(array($image->getMockID()))
       ->executeOne();
 
     $phids = array($mock->getAuthorPHID());
     $this->loadHandles($phids);
 
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($user);
     $engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION);
     $engine->process();
 
 
     $images = $mock->getImageHistorySet($this->imageID);
-    // TODO - this is a hack until we specialize the view object
     $mock->attachImages($images);
     $latest_image = last($images);
 
     $title = pht(
       'Image history for "%s" from the mock "%s."',
       $latest_image->getName(),
       $mock->getName());
 
     $header = id(new PhabricatorHeaderView())
       ->setHeader($title);
 
     require_celerity_resource('pholio-css');
     require_celerity_resource('pholio-inline-comments-css');
 
-    $comment_form_id = celerity_generate_unique_node_id();
+    $comment_form_id = null;
     $output = id(new PholioMockImagesView())
       ->setRequestURI($request->getRequestURI())
       ->setCommentFormID($comment_form_id)
       ->setUser($user)
       ->setMock($mock)
-      ->setImageID($this->imageID);
+      ->setImageID($this->imageID)
+      ->setViewMode('history');
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs
       ->addCrumb(
         id(new PhabricatorCrumbView())
         ->setName('M'.$mock->getID())
         ->setHref('/M'.$mock->getID()))
       ->addCrumb(
         id(new PhabricatorCrumbView())
         ->setName('Image History')
         ->setHref($request->getRequestURI()));
 
     $content = array(
       $crumbs,
       $header,
       $output->render(),
     );
 
     return $this->buildApplicationPage(
       $content,
       array(
         'title' => 'M'.$mock->getID().' '.$title,
         'device' => true,
         'pageObjects' => array($mock->getPHID()),
       ));
   }
 
 }
diff --git a/src/applications/pholio/view/PholioMockImagesView.php b/src/applications/pholio/view/PholioMockImagesView.php
index d8d8dd0563..0622d0a2a6 100644
--- a/src/applications/pholio/view/PholioMockImagesView.php
+++ b/src/applications/pholio/view/PholioMockImagesView.php
@@ -1,176 +1,202 @@
 <?php
 
 /**
  * @group pholio
  */
 final class PholioMockImagesView extends AphrontView {
 
   private $mock;
   private $imageID;
   private $requestURI;
   private $commentFormID;
+  private $viewMode = 'normal';
+
+  /**
+   * Supports normal (/MX, /MX/Y/) and history (/pholio/image/history/Y/)
+   * modes. The former has handy dandy commenting functionality and the
+   * latter does not.
+   */
+  public function setViewMode($view_mode) {
+    $this->viewMode = $view_mode;
+    return $this;
+  }
+  public function getViewMode() {
+    return $this->viewMode;
+  }
 
   public function setCommentFormID($comment_form_id) {
     $this->commentFormID = $comment_form_id;
     return $this;
   }
   public function getCommentFormID() {
     return $this->commentFormID;
   }
 
   public function setRequestURI(PhutilURI $request_uri) {
     $this->requestURI = $request_uri;
     return $this;
   }
   public function getRequestURI() {
     return $this->requestURI;
   }
 
   public function setImageID($image_id) {
     $this->imageID = $image_id;
     return $this;
   }
 
   public function getImageID() {
     return $this->imageID;
   }
 
   public function setMock(PholioMock $mock) {
     $this->mock = $mock;
     return $this;
   }
 
   public function render() {
     if (!$this->mock) {
       throw new Exception("Call setMock() before render()!");
     }
 
     $mock = $this->mock;
 
     require_celerity_resource('javelin-behavior-pholio-mock-view');
 
     $images = array();
     $panel_id = celerity_generate_unique_node_id();
     $viewport_id = celerity_generate_unique_node_id();
 
     $ids = mpull($mock->getImages(), 'getID');
     if ($this->imageID && isset($ids[$this->imageID])) {
       $selected_id = $this->imageID;
     } else {
       $selected_id = head_key($ids);
     }
 
     foreach ($mock->getImages() as $image) {
       $file = $image->getFile();
       $metadata = $file->getMetadata();
       $x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH);
       $y = idx($metadata, PhabricatorFile::METADATA_IMAGE_HEIGHT);
 
+      $history_uri = '/pholio/image/history/'.$image->getID().'/';
       $images[] = array(
         'id' => $image->getID(),
-        'fullURI' => $image->getFile()->getBestURI(),
-        'pageURI' => '/M'.$mock->getID().'/'.$image->getID().'/',
-        'historyURI' => '/pholio/image/history/'.$image->getID().'/',
+        'fullURI' => $file->getBestURI(),
+        'pageURI' => $this->getImagePageURI($image, $mock),
+        'historyURI' => $history_uri,
         'width' => $x,
         'height' => $y,
         'title' => $image->getName(),
         'desc' => $image->getDescription(),
+        'isObsolete' => (bool) $image->getIsObsolete(),
       );
     }
 
     $login_uri = id(new PhutilURI('/login/'))
       ->setQueryParam('next', (string) $this->getRequestURI());
     $config = array(
       'mockID' => $mock->getID(),
       'panelID' => $panel_id,
       'viewportID' => $viewport_id,
       'commentFormID' => $this->getCommentFormID(),
       'images' => $images,
       'selectedID' => $selected_id,
       'loggedIn' => $this->getUser()->isLoggedIn(),
-      'logInLink' => (string) $login_uri
+      'logInLink' => (string) $login_uri,
+      'viewMode' => $this->getViewMode()
     );
     Javelin::initBehavior('pholio-mock-view', $config);
 
     $mockview = '';
 
     $mock_wrapper = javelin_tag(
       'div',
       array(
         'id' => $viewport_id,
         'sigil' => 'mock-viewport',
         'class' => 'pholio-mock-image-viewport'
       ),
       '');
 
     $mock_wrapper = javelin_tag(
       'div',
       array(
         'id' => $panel_id,
         'sigil' => 'mock-panel touchable',
         'class' => 'pholio-mock-image-panel',
       ),
       $mock_wrapper);
 
     $inline_comments_holder = javelin_tag(
       'div',
       array(
         'id' => 'mock-inline-comments',
         'sigil' => 'mock-inline-comments',
         'class' => 'pholio-mock-inline-comments'
       ),
       '');
 
     $carousel_holder = '';
     if (count($mock->getImages()) > 0) {
       $thumbnails = array();
       foreach ($mock->getImages() as $image) {
         $thumbfile = $image->getFile();
 
         $dimensions = PhabricatorImageTransformer::getPreviewDimensions(
           $thumbfile,
           140);
 
         $tag = phutil_tag(
           'img',
           array(
             'width' => $dimensions['sdx'],
             'height' => $dimensions['sdy'],
             'src' => $thumbfile->getPreview140URI(),
             'class' => 'pholio-mock-carousel-thumbnail',
             'style' => 'top: '.floor((140 - $dimensions['sdy'] ) / 2).'px',
         ));
 
         $thumbnails[] = javelin_tag(
           'a',
           array(
             'sigil' => 'mock-thumbnail',
             'class' => 'pholio-mock-carousel-thumb-item',
-            'href' => '/M'.$mock->getID().'/'.$image->getID().'/',
+            'href' => $this->getImagePageURI($image, $mock),
             'meta' => array(
               'imageID' => $image->getID(),
             ),
           ),
           $tag);
       }
 
       $carousel_holder = phutil_tag(
         'div',
         array(
           'id' => 'pholio-mock-carousel',
           'class' => 'pholio-mock-carousel',
         ),
         $thumbnails);
     }
 
     $mockview[] = phutil_tag(
       'div',
         array(
           'class' => 'pholio-mock-image-container',
           'id' => 'pholio-mock-image-container'
         ),
       array($mock_wrapper, $carousel_holder, $inline_comments_holder));
 
     return $mockview;
   }
+
+  private function getImagePageURI(PholioImage $image, PholioMock $mock) {
+    if ($this->getViewMode() == 'normal') {
+      $uri = '/M'.$mock->getID().'/'.$image->getID().'/';
+    } else {
+      $uri = '/pholio/image/history/'.$image->getID().'/';
+    }
+    return $uri;
+  }
 }
diff --git a/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js b/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
index 964a86d21b..9fa332caa1 100644
--- a/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
+++ b/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
@@ -1,847 +1,855 @@
 /**
  * @provides javelin-behavior-pholio-mock-view
  * @requires 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
  */
 JX.behavior('pholio-mock-view', function(config) {
   var is_dragging = false;
 
   var drag_begin;
   var drag_end;
   var panel = JX.$(config.panelID);
   var viewport = JX.$(config.viewportID);
 
   var selection_border;
   var selection_fill;
   var active_image;
 
   var inline_comments = {};
 
 
 /* -(  Stage  )-------------------------------------------------------------- */
 
 
   var stage = (function() {
     var loading = false;
     var stageElement = JX.$(config.panelID);
     var viewElement = JX.$(config.viewportID);
     var gutterElement = JX.$('mock-inline-comments');
     var reticles = [];
     var cards = [];
     var inline_phid_map = {};
 
     function begin_load() {
       if (loading) {
         return;
       }
       loading = true;
       clear_stage();
       draw_loading();
     }
 
     function end_load() {
       if (!loading) {
         return;
       }
       loading = false;
       draw_loading();
     }
 
     function draw_loading() {
       JX.DOM.alterClass(stageElement, 'pholio-image-loading', loading);
     }
 
     function add_inline_node(node, phid) {
       inline_phid_map[phid] = (inline_phid_map[phid] || []);
       inline_phid_map[phid].push(node);
     }
 
     function add_reticle(reticle, phid) {
       mark_ref(reticle, phid);
 
       reticles.push(reticle);
       add_inline_node(reticle, phid);
 
       viewElement.appendChild(reticle);
     }
 
     function clear_stage() {
       var ii;
       for (ii = 0; ii < reticles.length; ii++) {
         JX.DOM.remove(reticles[ii]);
       }
       for (ii = 0; ii < cards.length; ii++) {
         JX.DOM.remove(cards[ii]);
       }
       reticles = [];
       cards = [];
       inline_phid_map = {};
     }
 
     function highlight_inline(phid, show) {
       var nodes = inline_phid_map[phid] || [];
       var cls = 'pholio-mock-inline-comment-highlight';
       for (var ii = 0; ii < nodes.length; ii++) {
         JX.DOM.alterClass(nodes[ii], cls, show);
       }
     }
 
     function remove_inline(phid) {
       var nodes = inline_phid_map[phid] || [];
       for (var ii = 0; ii < nodes.length; ii++) {
         JX.DOM.remove(nodes[ii]);
       }
       delete inline_phid_map[phid];
     }
 
     function mark_ref(node, phid) {
       JX.Stratcom.addSigil(node, 'pholio-inline-ref');
       JX.Stratcom.addData(node, {phid: phid});
     }
 
     function add_card(card, phid) {
       mark_ref(card, phid);
 
       cards.push(card);
       add_inline_node(card, phid);
 
       gutterElement.appendChild(card);
     }
 
     return {
       beginLoad: begin_load,
       endLoad: end_load,
       addReticle: add_reticle,
       clearStage: clear_stage,
       highlightInline: highlight_inline,
       removeInline: remove_inline,
       addCard: add_card
     };
   })();
 
   function get_image_index(id) {
     for (var ii = 0; ii < config.images.length; ii++) {
       if (config.images[ii].id == id) {
         return ii;
       }
     }
     return null;
   }
 
   function get_image(id) {
     var idx = get_image_index(id);
     if (idx === null) {
       return idx;
     }
     return config.images[idx];
   }
 
   function onload_image(id) {
     if (active_image.id != id) {
       // The user has clicked another image before this one loaded, so just
       // bail.
       return;
     }
 
     active_image.tag = this;
     redraw_image();
   }
 
   function switch_image(delta) {
     if (!active_image) {
       return;
     }
     var idx = get_image_index(active_image.id);
     idx = (idx + delta + config.images.length) % config.images.length;
     select_image(config.images[idx].id);
   }
 
   function redraw_image() {
 
     // Force the stage to scale as a function of the viewport size. Broadly,
     // we make the stage 95% of the height of the viewport, then scale images
     // to fit within it.
     var new_y = (JX.Vector.getViewport().y * 0.90) - 150;
     new_y = Math.max(320, new_y);
     panel.style.height = new_y + 'px';
 
     if (!active_image || !active_image.tag) {
       return;
     }
 
     var tag = active_image.tag;
 
     // If the image is too wide or tall for the viewport, scale it down so it
     // fits.
     var w = JX.Vector.getDim(panel);
     w.x -= 40;
     w.y -= 40;
     var scale = 1;
     if (w.x < tag.naturalWidth) {
       scale = Math.min(scale, w.x / tag.naturalWidth);
     }
     if (w.y < tag.naturalHeight) {
       scale = Math.min(scale, w.y / tag.naturalHeight);
     }
 
     if (scale < 1) {
       tag.width = Math.floor(scale * tag.naturalWidth);
       tag.height = Math.floor(scale * tag.naturalHeight);
     } else {
       tag.width = tag.naturalWidth;
       tag.height = tag.naturalHeight;
     }
 
     viewport.style.top = Math.floor((new_y - tag.height) / 2) + 'px';
 
     stage.endLoad();
 
     JX.DOM.setContent(viewport, tag);
 
     redraw_inlines(active_image.id);
   }
 
   function select_image(image_id) {
     active_image = get_image(image_id);
     active_image.tag = null;
 
     stage.beginLoad();
 
     var img = JX.$N('img', {className: 'pholio-mock-image'});
     img.onload = JX.bind(img, onload_image, active_image.id);
     img.src = active_image.fullURI;
 
     var thumbs = JX.DOM.scry(
       JX.$('pholio-mock-carousel'),
       'a',
       'mock-thumbnail');
 
     for(var k in thumbs) {
       var thumb_meta = JX.Stratcom.getData(thumbs[k]);
 
       JX.DOM.alterClass(
         thumbs[k],
         'pholio-mock-carousel-thumb-current',
         (active_image.id == thumb_meta.imageID));
     }
 
     load_inline_comments();
 
     if (image_id != config.selectedID) {
       JX.History.replace(active_image.pageURI);
     }
   }
 
   JX.Stratcom.listen(
     ['mousedown', 'click'],
     'mock-thumbnail',
     function(e) {
       if (!e.isNormalMouseEvent()) {
         return;
       }
       e.kill();
       select_image(e.getNodeData('mock-thumbnail').imageID);
     });
 
   select_image(config.selectedID);
 
   JX.Stratcom.listen('mousedown', 'mock-viewport', function(e) {
     if (!e.isNormalMouseEvent()) {
       return;
     }
 
     if (JX.Device.getDevice() != 'desktop') {
       return;
     }
 
+    if (config.viewMode == 'history') {
+      return;
+    }
+
     if (drag_begin) {
       return;
     }
 
     e.kill();
 
     is_dragging = true;
     drag_begin = get_image_xy(JX.$V(e));
     drag_end = drag_begin;
 
     redraw_selection();
   });
 
 
   JX.enableDispatch(document.body, 'mousemove');
   JX.Stratcom.listen('mousemove', null, function(e) {
     if (!is_dragging) {
       return;
     }
     drag_end = get_image_xy(JX.$V(e));
     redraw_selection();
   });
 
   JX.Stratcom.listen(
     ['mouseover', 'mouseout'],
     'pholio-inline-ref',
     function(e) {
       var phid = e.getNodeData('pholio-inline-ref').phid;
       var show = (e.getType() == 'mouseover');
       stage.highlightInline(phid, show);
     });
 
   JX.Stratcom.listen(
     'mouseup',
     null,
     function(e) {
       if (!is_dragging) {
         return;
       }
 
       is_dragging = false;
       if (!config.loggedIn) {
         new JX.Workflow(config.logInLink).start();
         return;
       }
 
       drag_end = get_image_xy(JX.$V(e));
       var scale = get_image_scale();
 
       resize_selection(16);
 
       var data = {mockID: config.mockID};
       var handler = function(r) {
         var dialog = JX.$H(r).getFragment().firstChild;
         JX.DOM.appendContent(viewport, dialog);
 
         var x = Math.min(drag_begin.x * scale, drag_end.x * scale);
         var y = Math.max(drag_begin.y * scale, drag_end.y * scale) + 4;
         JX.$V(x, y).setPos(dialog);
 
         JX.DOM.focus(JX.DOM.find(dialog, 'textarea'));
       };
 
       new JX.Workflow('/pholio/inline/save/', data)
         .setHandler(handler)
         .start();
     });
 
   function resize_selection(min_size) {
     var start = {
       x: Math.min(drag_begin.x, drag_end.x),
       y: Math.min(drag_begin.y, drag_end.y)
     };
     var end = {
       x: Math.max(drag_begin.x, drag_end.x),
       y: Math.max(drag_begin.y, drag_end.y)
     };
 
     var width = end.x - start.x;
     var height = end.y - start.y;
     var addon;
 
     if (width < min_size) {
       addon = (min_size-width)/2;
 
       start.x = Math.max(0, start.x - addon);
       end.x = Math.min(active_image.tag.naturalWidth, end.x + addon);
 
       if (start.x === 0) {
         end.x = Math.min(min_size, active_image.tag.naturalWidth);
       } else if (end.x == active_image.tag.naturalWidth) {
         start.x = Math.max(0, active_image.tag.naturalWidth - min_size);
       }
     }
 
     if (height < min_size) {
       addon = (min_size-height)/2;
 
       start.y = Math.max(0, start.y - addon);
       end.y = Math.min(active_image.tag.naturalHeight, end.y + addon);
 
       if (start.y === 0) {
         end.y = Math.min(min_size, active_image.tag.naturalHeight);
       } else if (end.y == active_image.tag.naturalHeight) {
         start.y = Math.max(0, active_image.tag.naturalHeight - min_size);
       }
     }
 
     drag_begin = start;
     drag_end = end;
     redraw_selection();
   }
 
   function redraw_inlines(id) {
     if (!active_image) {
       return;
     }
 
     if (active_image.id != id) {
       return;
     }
 
     stage.clearStage();
     var comment_holder = JX.$('mock-inline-comments');
     JX.DOM.setContent(comment_holder, render_image_info(active_image));
 
     var inlines = inline_comments[active_image.id];
     if (!inlines || !inlines.length) {
       return;
     }
 
     for (var ii = 0; ii < inlines.length; ii++) {
       var inline = inlines[ii];
       var card = JX.$H(inline.contentHTML).getFragment().firstChild;
 
       stage.addCard(card, inline.phid);
 
       if (!active_image.tag) {
         // The image itself hasn't loaded yet, so we can't draw the inline
         // reticles.
         continue;
       }
 
       var inline_selection = render_reticle_fill();
       stage.addReticle(inline_selection, inline.phid);
       position_inline_rectangle(inline, inline_selection);
 
       if (!inline.transactionphid) {
         var inline_draft = render_reticle_border();
         stage.addReticle(inline_draft, inline.phid);
         position_inline_rectangle(inline, inline_draft);
       }
     }
   }
 
   function position_inline_rectangle(inline, rect) {
     var scale = get_image_scale();
 
     JX.$V(scale * inline.x, scale * inline.y).setPos(rect);
     JX.$V(scale * inline.width, scale * inline.height).setDim(rect);
   }
 
   function get_image_xy(p) {
     var img = active_image.tag;
     var imgp = JX.$V(img);
 
     var scale = 1 / get_image_scale();
 
     var x = scale * Math.max(0, Math.min(p.x - imgp.x, img.width));
     var y = scale * Math.max(0, Math.min(p.y - imgp.y, img.height));
 
     return {
       x: x,
       y: y
     };
   }
 
   function get_image_scale() {
     var img = active_image.tag;
     return Math.min(
       img.width / img.naturalWidth,
       img.height / img.naturalHeight);
   }
 
   function redraw_selection() {
     selection_border = selection_border || render_reticle_border();
     selection_fill = selection_fill || render_reticle_fill();
 
     var p = JX.$V(
       Math.min(drag_begin.x, drag_end.x),
       Math.min(drag_begin.y, drag_end.y));
 
     var d = JX.$V(
       Math.max(drag_begin.x, drag_end.x) - p.x,
       Math.max(drag_begin.y, drag_end.y) - p.y);
 
     var scale = get_image_scale();
 
     p.x *= scale;
     p.y *= scale;
     d.x *= scale;
     d.y *= scale;
 
     var nodes = [selection_fill, selection_border];
     for (var ii = 0; ii < nodes.length; ii++) {
       var node = nodes[ii];
       viewport.appendChild(node);
       p.setPos(node);
       d.setDim(node);
     }
   }
 
   function clear_selection() {
     selection_fill && JX.DOM.remove(selection_fill);
     selection_border && JX.DOM.remove(selection_border);
   }
 
   function load_inline_comments() {
     var id = active_image.id;
     var inline_comments_uri = "/pholio/inline/" + id + "/";
 
     new JX.Request(inline_comments_uri, function(r) {
       inline_comments[id] = r;
       redraw_inlines(id);
     }).send();
   }
 
   JX.Stratcom.listen(
     'click',
     'inline-delete',
     function(e) {
       var data = e.getNodeData('inline-delete');
       e.kill();
       interrupt_typing();
 
       stage.removeInline(data.phid);
 
       var deleteURI = '/pholio/inline/delete/' + data.id + '/';
       var del = new JX.Request(deleteURI, function(r) {
 
         });
       del.send();
 
     });
 
   JX.Stratcom.listen(
     'click',
     'inline-edit',
     function(e) {
       var data = e.getNodeData('inline-edit');
       e.kill();
 
       interrupt_typing();
 
       var editURI = "/pholio/inline/edit/" + data.id + '/';
 
       var edit_dialog = new JX.Request(editURI, function(r) {
         var dialog = JX.$N(
           'div',
           {
             className: 'pholio-edit-inline-popup'
           },
           JX.$H(r));
 
         JX.DOM.setContent(JX.$(data.phid + '_comment'), dialog);
       });
 
       edit_dialog.send();
     });
 
   JX.Stratcom.listen(
     'click',
     'inline-edit-cancel',
     function(e) {
       var data = e.getNodeData('inline-edit-cancel');
       e.kill();
       load_inline_comment(data.id);
   });
 
   JX.Stratcom.listen(
     'click',
     'inline-edit-submit',
     function(e) {
       var data = e.getNodeData('inline-edit-submit');
       var editURI = "/pholio/inline/edit/" + data.id + '/';
       e.kill();
 
       var edit = new JX.Request(editURI, function(r) {
         load_inline_comment(data.id);
         JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
       });
       edit.addData({
         op: 'update',
         content: JX.DOM.find(JX.$(data.phid + '_comment'), 'textarea').value
       });
       edit.send();
   });
 
   JX.Stratcom.listen(
     'click',
     'inline-save-cancel',
     function(e) {
       e.kill();
       interrupt_typing();
     }
   );
 
   JX.Stratcom.listen(
     'click',
     'inline-save-submit',
     function(e) {
       e.kill();
 
       var form = JX.$('pholio-new-inline-comment-dialog');
       var text = JX.DOM.find(form, 'textarea').value;
       if (!text.length) {
         interrupt_typing();
         return;
       }
 
       var data = {
         mockID: config.mockID,
         imageID: active_image.id,
         startX: Math.min(drag_begin.x, drag_end.x),
         startY: Math.min(drag_begin.y, drag_end.y),
         endX: Math.max(drag_begin.x, drag_end.x),
         endY: Math.max(drag_begin.y, drag_end.y)
       };
 
       var handler = function(r) {
         if (!inline_comments[active_image.id]) {
           inline_comments[active_image.id] = [];
         }
         inline_comments[active_image.id].push(r);
 
         interrupt_typing();
         redraw_inlines(active_image.id);
         JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
       };
 
       JX.Workflow.newFromForm(form, data)
         .setHandler(handler)
         .start();
     }
   );
 
   function load_inline_comment(id) {
     var viewInlineURI = '/pholio/inline/view/' + id + '/';
     var inline_comment = new JX.Request(viewInlineURI, function(r) {
       JX.DOM.replace(JX.$(r.phid + '_comment'), JX.$H(r.contentHTML));
     });
     inline_comment.send();
   }
 
   function interrupt_typing() {
     clear_selection();
 
     try {
       JX.DOM.remove(JX.$('pholio-new-inline-comment-dialog'));
     } catch (x) {
       // TODO: For now, ignore this.
     }
 
     drag_begin = null;
   }
 
   load_inline_comments();
-  if (config.loggedIn) {
+  if (config.loggedIn && config.commentFormID) {
     JX.DOM.invoke(JX.$(config.commentFormID), 'shouldRefresh');
   }
 
   JX.Stratcom.listen('resize', null, redraw_image);
   redraw_image();
 
 
 /* -(  Keyboard Shortcuts  )------------------------------------------------- */
 
 
   new JX.KeyboardShortcut(['j', 'right'], 'Show next image.')
     .setHandler(function() {
       switch_image(1);
     })
     .register();
 
   new JX.KeyboardShortcut(['k', 'left'], 'Show previous image.')
     .setHandler(function() {
       switch_image(-1);
     })
     .register();
 
   JX.DOM.listen(panel, 'gesture.swipe.end', null, function(e) {
     var data = e.getData();
 
     if (data.length <= (JX.Vector.getDim(panel) / 2)) {
       // If the user didn't move their finger far enough, don't switch.
       return;
     }
 
     switch_image(data.direction == 'right' ? -1 : 1);
   });
 
 /* -(  Render  )------------------------------------------------------------- */
 
 
   function render_image_info(image) {
     var info = [];
 
     var title = JX.$N(
       'div',
       {className: 'pholio-image-title'},
       image.title);
     info.push(title);
 
     var desc = JX.$N(
       'div',
       {className: 'pholio-image-description'},
       image.desc);
     info.push(desc);
 
-    var embed = JX.$N(
-      'div',
-      {className: 'pholio-image-embedding'},
-      JX.$H('Embed this image:<br />{M' + config.mockID +
+    if (!image.isObsolete) {
+      var embed = JX.$N(
+        'div',
+        {className: 'pholio-image-embedding'},
+        JX.$H('Embed this image:<br />{M' + config.mockID +
         ', image=' + image.id + '}'));
-    info.push(embed);
+      info.push(embed);
+    }
 
     // Render image dimensions and visible size. If we have this infomation
     // from the server we can display some of it immediately; otherwise, we need
     // to wait for the image to load so we can read dimension information from
     // it.
 
     var image_x = image.width;
     var image_y = image.height;
     var display_x = null;
     if (image.tag) {
       image_x = image.tag.naturalWidth;
       image_y = image.tag.naturalHeight;
       display_x = image.tag.width;
     }
 
     var visible = [];
     if (image_x) {
       visible.push([image_x, '\u00d7', image_y, 'px']);
       if (display_x) {
         var area = Math.round(100 * (display_x / image_x));
         visible.push(' ');
         visible.push(
           JX.$N(
             'span',
             {className: 'pholio-visible-size'},
             ['(', area, '%', ')']));
       }
     }
 
     if (visible.length) {
       info.push(visible);
     }
 
     var full_link = JX.$N(
       'a',
       {href: image.fullURI, target: '_blank'},
       'View Full Image');
     info.push(full_link);
 
-    var history_link = JX.$N(
-      'a',
-      { href: image.historyURI },
-      'View Image History');
-    info.push(history_link);
+    if (config.viewMode != 'history') {
+      var history_link = JX.$N(
+        'a',
+        { href: image.historyURI },
+        'View Image History');
+      info.push(history_link);
+    }
 
 
     for (var ii = 0; ii < info.length; ii++) {
       info[ii] = JX.$N('div', {className: 'pholio-image-info-item'}, info[ii]);
     }
     info = JX.$N('div', {className: 'pholio-image-info'}, info);
 
     return info;
   }
 
   function render_reticle_border() {
     return JX.$N(
       'div',
       {className: 'pholio-mock-select-border'});
   }
 
   function render_reticle_fill() {
     return JX.$N(
       'div',
       {className: 'pholio-mock-select-fill'});
   }
 
 
 /* -(  Device Lightbox  )---------------------------------------------------- */
 
   // On devices, we show images full-size when the user taps them instead of
   // attempting to implement inlines.
 
   var lightbox = null;
 
   JX.Stratcom.listen('click', 'mock-viewport', function(e) {
     if (!e.isNormalMouseEvent()) {
       return;
     }
     if (JX.Device.getDevice() == 'desktop') {
       return;
     }
     lightbox_attach();
     e.kill();
   });
 
   JX.Stratcom.listen('click', 'pholio-device-lightbox', lightbox_detach);
   JX.Stratcom.listen('resize', null, lightbox_resize);
 
   function lightbox_attach() {
     JX.DOM.alterClass(document.body, 'lightbox-attached', true);
     JX.Mask.show('jx-dark-mask');
 
     lightbox = lightbox_render();
     var image = JX.$N('img');
     image.onload = lightbox_loaded;
     setTimeout(function() {
       image.src = active_image.fullURI;
     }, 1000);
     JX.DOM.setContent(lightbox, image);
     JX.DOM.alterClass(lightbox, 'pholio-device-lightbox-loading', true);
 
     lightbox_resize();
 
     document.body.appendChild(lightbox);
   }
 
   function lightbox_detach() {
     JX.DOM.remove(lightbox);
     JX.Mask.hide();
     JX.DOM.alterClass(document.body, 'lightbox-attached', false);
     lightbox = null;
   }
 
   function lightbox_resize(e) {
     if (!lightbox) {
       return;
     }
     JX.Vector.getScroll().setPos(lightbox);
     JX.Vector.getViewport().setDim(lightbox);
   }
 
   function lightbox_loaded() {
     JX.DOM.alterClass(lightbox, 'pholio-device-lightbox-loading', false);
   }
 
   function lightbox_render() {
     var el = JX.$N('div', {className: 'pholio-device-lightbox'});
     JX.Stratcom.addSigil(el, 'pholio-device-lightbox');
     return el;
   }
 
 
 /* -(  Preload  )------------------------------------------------------------ */
 
   var preload = [];
   for (var ii = 0; ii < config.images.length; ii++) {
     preload.push(config.images[ii].fullURI);
   }
 
   function preload_next() {
     next_src = preload[0];
     if (!next_src) {
       return;
     }
     preload.splice(0, 1);
 
     var img = JX.$N('img');
     img.onload = preload_next;
     img.onerror = preload_next;
     img.src = next_src;
   }
 
   preload_next();
 
 
 });