diff --git a/css/thread.css b/css/thread.css
index 4ac6615703681721278f9d2f8815c499d2a6aff9..5d8ecb7a2014832c05decdcaf34321399cf4f68e 100644
--- a/css/thread.css
+++ b/css/thread.css
@@ -66,7 +66,7 @@
 .thread-body kbd,
 .thread-body pre,
 .thread-body samp {
-  font-family: monospace, serif;
+  font-family: 'Source Code Pro', 'Monaco', 'Consolas', monospace, serif;
   font-size: 1em;
 }
 .thread-body pre {
@@ -420,7 +420,10 @@
 	margin: 0;
 	margin-bottom: 10px;
 	border: none;
-	background: none !important;
+    background: #f5f5f5;
+    background-color: rgba(0,0,0,0.05);
+    border-radius: 5px;
+    padding: 0.5em;
 	box-shadow: none !important;
     text-indent: 0 !important;
 }
diff --git a/include/class.orm.php b/include/class.orm.php
index d8e70f6007b086fe77ab20ab0a79da96c97780eb..676390409859f92f996ab167e3987bdf8a304a66 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -588,6 +588,7 @@ class AnnotatedModel {
             return $this->annotations[$what];
         return $this->model->get($what, null);
     }
+
     function __set($what, $to) {
         return $this->set($what, $to);
     }
@@ -597,6 +598,10 @@ class AnnotatedModel {
         return $this->model->set($what, $to);
     }
 
+    function __isset($what) {
+        return isset($this->annotations[$what]) || $this->model->__isset($what);
+    }
+
     // Delegate everything else to the model
     function __call($what, $how) {
         return call_user_func_array(array($this->model, $what), $how);
diff --git a/include/class.thread.php b/include/class.thread.php
index 2b8be9fa597ec0ecbdcef95a24088f04a8cfd2e7..00623c85eb1020c12bc70e7500a8bed0ea191c20 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -381,10 +381,10 @@ class Thread extends VerySimpleModel {
             // Try not to destroy the format of the body
             $header = sprintf("Received From: %s <%s>\n\n", $mailinfo['name'],
                 $mailinfo['email']);
-            if ($body instanceof HtmlThreadBody)
+            if ($body instanceof HtmlThreadEntryBody)
                 $header = nl2br(Format::htmlchars($header));
             // Add the banner to the top of the message
-            if ($body instanceof ThreadBody)
+            if ($body instanceof ThreadEntryBody)
                 $body->prepend($header);
             $vars['message'] = $body;
             $vars['userId'] = 0; //Unknown user! //XXX: Assume ticket owner?
@@ -565,6 +565,7 @@ implements TemplateVariable {
     const FLAG_EDITED                   = 0x0002;
     const FLAG_HIDDEN                   = 0x0004;
     const FLAG_GUARDED                  = 0x0008;   // No replace on edit
+    const FLAG_RESENT                   = 0x0010;
 
     const PERM_EDIT     = 'thread.edit';
 
@@ -762,6 +763,17 @@ implements TemplateVariable {
         return $this->user;
     }
 
+    function getEditor() {
+        static $types = array(
+            'U' => 'User',
+            'S' => 'Staff',
+        );
+        if (!isset($types[$this->editor_type]))
+            return null;
+
+        return $types[$this->editor_type]::lookup($this->editor);
+    }
+
     function getName() {
         if ($this->staff_id)
             return $this->staff->getName();
@@ -1527,6 +1539,7 @@ class ThreadEvent extends VerySimpleModel {
             'edited'    => 'pencil',
             'closed'    => 'thumbs-up-alt',
             'reopened'  => 'rotate-right',
+            'resent'    => 'reply-all icon-flip-horizontal',
         );
         return @$icons[$this->state] ?: 'chevron-sign-right';
     }
@@ -1595,6 +1608,7 @@ class ThreadEvent extends VerySimpleModel {
                     return '';
                 return sprintf($base, implode(', ', $changes));
             },
+            'resent' => __('<b>{username}</b> resent <strong><a href="#thread-entry-{data.entry}">a previous response</a></strong> {timestamp}'),
         );
         $self = $this;
         $data = $this->getData();
diff --git a/include/class.thread_actions.php b/include/class.thread_actions.php
index 15758498fdabb968929300fded2c0402f3af7610..df4cf37e02e1d48cb087a47b293053d20ab17236 100644
--- a/include/class.thread_actions.php
+++ b/include/class.thread_actions.php
@@ -88,12 +88,12 @@ $.dialog(url, [201], function(xhr, resp) {
   var json = JSON.parse(resp);
   if (!json || !json.thread_id)
     return;
-  $('#thread-id-'+json.thread_id)
-    .attr('id', 'thread-id-' + json.new_id)
-    .find('div')
-    .html(json.body)
-    .closest('td')
-    .effect('highlight')
+  $('#thread-entry-'+json.thread_id)
+    .attr('id', 'thread-entry-' + json.new_id)
+    .html(json.entry)
+    .find('.thread-body')
+    .delay(500)
+    .effect('highlight');
 }, {size:'large'});
 JS
         , $this->getAjaxUrl());
@@ -118,10 +118,10 @@ JS
     }
 
     function updateEntry($guard=false) {
+        global $thisstaff;
+
         $old = $this->entry;
-        $type = ($old->format == 'html')
-            ? 'HtmlThreadEntryBody' : 'TextThreadEntryBody';
-        $new = new $type($_POST['body']);
+        $new = ThreadEntryBody::fromFormattedText($_POST['body'], $old->format);
 
         if ($new->getClean() == $old->body)
             // No update was performed
@@ -139,7 +139,7 @@ JS
             'pid' => $old->id,
 
             // Add in new stuff
-            'title' => $_POST['title'],
+            'title' => Format::htmlchars($_POST['title']),
             'body' => $new,
             'ip_address' => $_SERVER['REMOTE_ADDR'],
         ));
@@ -151,6 +151,8 @@ JS
         // that way for email header lookups and such to remain consistent
 
         if ($old->flags & ThreadEntry::FLAG_EDITED
+            // If editing another person's edit, make a new entry
+            and ($old->editor == $thisstaff->getId() && $old->editor_type == 'S')
             and !($old->flags & ThreadEntry::FLAG_GUARDED)
         ) {
             // Replace previous edit --------------------------
@@ -162,20 +164,24 @@ JS
             $old = $original;
         }
 
-        // Mark the new entry as edited (but not hidden)
-        $entry->flags = ($old->flags & ~ThreadEntry::FLAG_HIDDEN)
+        // Mark the new entry as edited (but not hidden nor guarded)
+        $entry->flags = ($old->flags & ~(ThreadEntry::FLAG_HIDDEN | ThreadEntry::FLAG_GUARDED))
             | ThreadEntry::FLAG_EDITED;
 
         // Guard against deletes on future edit if requested. This is done
         // if an email was triggered by the last edit. In such a case, it
-        // should not be replace by a subsequent edit.
+        // should not be replaced by a subsequent edit.
         if ($guard)
             $entry->flags |= ThreadEntry::FLAG_GUARDED;
 
-        // Sort in the same place in the thread — XXX: Add a `sequence` id
+        // Log the editor
+        $entry->editor = $thisstaff->getId();
+        $entry->editor_type = 'S';
+
+        // Sort in the same place in the thread
         $entry->created = $old->created;
         $entry->updated = SqlFunction::NOW();
-        $entry->save();
+        $entry->save(true);
 
         // Hide the old entry from the object thread
         $old->flags |= ThreadEntry::FLAG_HIDDEN;
@@ -190,10 +196,14 @@ JS
         if (!($entry = $this->updateEntry()))
             return $this->trigger__get();
 
+        ob_start();
+        include STAFFINC_DIR . 'templates/thread-entry.tmpl.php';
+        $content = ob_get_clean();
+
         Http::response('201', JsonDataEncoder::encode(array(
-            'thread_id' => $this->entry->id,
+            'thread_id' => $this->entry->id, # This is the old id!
             'new_id' => $entry->id,
-            'body' => $entry->getBody()->toHtml(),
+            'entry' => $content,
         )));
     }
 }
@@ -250,13 +260,17 @@ class TEA_EditAndResendThreadEntry extends TEA_EditThreadEntry {
         if (!($entry = $this->updateEntry($resend)))
             return $this->trigger__get();
 
-        if (@$_POST['commit'] == 'resend')
+        if ($resend)
             $this->resend($entry);
 
+        ob_start();
+        include STAFFINC_DIR . 'templates/thread-entry.tmpl.php';
+        $content = ob_get_clean();
+
         Http::response('201', JsonDataEncoder::encode(array(
-            'thread_id' => $this->entry->id,
+            'thread_id' => $this->entry->id, # This is the old id!
             'new_id' => $entry->id,
-            'body' => $entry->getBody()->toHtml(),
+            'entry' => $content,
         )));
     }
 
@@ -299,6 +313,13 @@ class TEA_EditAndResendThreadEntry extends TEA_EditThreadEntry {
         }
         // TODO: Add an option to the dialog
         $ticket->notifyCollaborators($response, array('signature' => $signature));
+
+        // Log an event that the item was resent
+        $ticket->logEvent('resent', array('entry' => $response->id));
+
+        // Flag the entry as resent
+        $response->flags |= ThreadEntry::FLAG_RESENT;
+        $response->save();
     }
 }
 ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_EditAndResendThreadEntry');
diff --git a/include/staff/templates/thread-entries.tmpl.php b/include/staff/templates/thread-entries.tmpl.php
index 0be5722c5004d59e9bf62415af5413f5c249e247..7ac199444b8b2607346451a97e5435cb5dda0753 100644
--- a/include/staff/templates/thread-entries.tmpl.php
+++ b/include/staff/templates/thread-entries.tmpl.php
@@ -28,7 +28,9 @@ if (count($entries)) {
                 $events->next();
                 $event = $events->current();
             }
+            ?><div id="thread-entry-<?php echo $entry->getId(); ?>"><?php
             include STAFFINC_DIR . 'templates/thread-entry.tmpl.php';
+            ?></div><?php
         }
         $i++;
     }
diff --git a/include/staff/templates/thread-entry-edit.tmpl.php b/include/staff/templates/thread-entry-edit.tmpl.php
index bab5e4eb65223cb4705a8499a3909ed58a404b7b..1440780061aa0831d3009522a54f501c63425ca4 100644
--- a/include/staff/templates/thread-entry-edit.tmpl.php
+++ b/include/staff/templates/thread-entry-edit.tmpl.php
@@ -33,7 +33,7 @@
     class="large <?php
         if ($cfg->isRichTextEnabled() && $this->entry->format == 'html')
             echo 'richtext';
-    ?>"><?php echo Format::viewableImages($this->entry->body);
+    ?>"><?php echo htmlspecialchars(Format::viewableImages($this->entry->body));
 ?></textarea>
 
 <?php if ($this->entry->type == 'R') { ?>
diff --git a/include/staff/templates/thread-entry-view.tmpl.php b/include/staff/templates/thread-entry-view.tmpl.php
index 51a0a10da8bc411507c049337ee7cc2b92a86e5b..0b5a542bae73f87eadfa31a810487042c05e54f5 100644
--- a/include/staff/templates/thread-entry-view.tmpl.php
+++ b/include/staff/templates/thread-entry-view.tmpl.php
@@ -16,7 +16,7 @@ do {
         // If you originally posted it, you can see all the edits
         && $E->staff_id != $thisstaff->getId()
         // You can see your own edits
-        //  && $E->editor != $thisstaff->getId()
+        && ($E->editor != $thisstaff->getId() || $E->editor_type != 'S')
     ) {
         // Skip edits made by other agents
         continue;
@@ -26,7 +26,8 @@ do {
     <strong><?php if ($E->title)
         echo Format::htmlchars($E->title).' — '; ?></strong>
     <em><?php if (strpos($E->updated, '0000-') === false)
-        echo sprintf(__('Edited on %s'), Format::datetime($E->updated));
+        echo sprintf(__('Edited on %s by %s'), Format::datetime($E->updated),
+            ($editor = $E->getEditor()) ? $editor->getName() : '');
     else
         echo __('Original'); ?></em>
     </a>
diff --git a/include/staff/templates/thread-entry.tmpl.php b/include/staff/templates/thread-entry.tmpl.php
index e7c31e2a512eb6eb614b9e88910dafc3a0a32107..e339e04555ebcbddc5d823f3edd6b7472956f080 100644
--- a/include/staff/templates/thread-entry.tmpl.php
+++ b/include/staff/templates/thread-entry.tmpl.php
@@ -38,9 +38,13 @@ if ($user && ($url = $user->get_gravatar(48)))
         <span style="vertical-align:middle;" class="textra">
 <?php   if ($entry->flags & ThreadEntry::FLAG_EDITED) { ?>
             <span class="label label-bare" title="<?php
-        echo sprintf(__('Edited on %s by %s'), Format::datetime($entry->updated), 'You');
+            echo sprintf(__('Edited on %s by %s'), Format::datetime($entry->updated),
+                ($editor = $entry->getEditor()) ? $editor->getName() : '');
                 ?>"><?php echo __('Edited'); ?></span>
-        <?php } ?>
+<?php   } ?>
+<?php   if ($entry->flags & ThreadEntry::FLAG_RESENT) { ?>
+            <span class="label label-bare"><?php echo __('Resent'); ?></span>
+<?php   } ?>
         </span>
         </div>
 <?php
@@ -56,11 +60,15 @@ if ($user && ($url = $user->get_gravatar(48)))
             echo $entry->title; ?></span>
         </span>
     </div>
-    <div class="thread-body" id="thread-id-<?php echo $entry->getId(); ?>">
+    <div class="thread-body">
         <div><?php echo $entry->getBody()->toHtml(); ?></div>
         <div class="clear"></div>
 <?php
-    if ($entry->has_attachments) { ?>
+    // The strangeness here is because .has_attachments is an annotation from
+    // Thread::getEntries(); however, this template may be used in other
+    // places such as from thread entry editing
+    if (isset($entry->has_attachments) ? $entry->has_attachments
+            : $entry->attachments->filter(array('inline'=>0))->count()) { ?>
     <div class="attachments"><?php
         foreach ($entry->attachments as $A) {
             if ($A->inline)
@@ -83,7 +91,7 @@ if ($user && ($url = $user->get_gravatar(48)))
 <?php
     if ($urls = $entry->getAttachmentUrls()) { ?>
         <script type="text/javascript">
-            $('#thread-id-<?php echo $entry->getId(); ?>')
+            $('#thread-entry-<?php echo $entry->getId(); ?>')
                 .data('urls', <?php
                     echo JsonDataEncoder::encode($urls); ?>)
                 .data('id', <?php echo $entry->getId(); ?>);
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 0181a06d0fa67ae762e1e1c779de278fb5ab1350..5bd0cf2ffb60593fc49e16a9ea5af2972df3ce6a 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -2391,6 +2391,9 @@ td.indented {
     padding: 0 2px 15px;
     margin-left: 60px;
 }
+.thread-event a {
+    color: inherit;
+}
 .type-icon {
     border-radius: 8px;
     background-color: #f4f4f4;
diff --git a/scp/js/scp.js b/scp/js/scp.js
index ef9fd3071ba409b70e3d42f710a589ed0fc26a28..fad761841fa6a6e82b811518ab3143398617972d 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -1071,6 +1071,7 @@ function addSearchParam(key, value) {
 
 // Periodically adjust relative times
 window.relativeAdjust = setInterval(function() {
+  // Thanks, http://stackoverflow.com/a/7641822/1025836
   var prettyDate = function(time) {
     var date = new Date((time || "").replace(/-/g, "/").replace(/[TZ]/g, " ")),
         diff = (((new Date()).getTime() - date.getTime()) / 1000),
@@ -1083,7 +1084,7 @@ window.relativeAdjust = setInterval(function() {
       || diff < 120 && __("about a minute ago")
       || diff < 3600 && __("%d minutes ago").replace('%d', Math.floor(diff/60))
       || diff < 7200 && __("about an hour ago")
-      || diff < 86400 &&  __("%d hours ago").replace('%d', Math.floor(diff/86400))
+      || diff < 86400 &&  __("%d hours ago").replace('%d', Math.floor(diff/3600))
     )
     || day_diff == 1 && __("yesterday")
     || day_diff < 7 && __("%d days ago").replace('%d', day_diff);
diff --git a/scp/js/ticket.js b/scp/js/ticket.js
index 7f7b879e7eb8a13f0723ee764dd668398ff5fdca..3be939612745b3aec9ae319af1903d885cbd918f 100644
--- a/scp/js/ticket.js
+++ b/scp/js/ticket.js
@@ -305,7 +305,7 @@ $.showNonLocalImage = function(div) {
 $.showImagesInline = function(urls, thread_id) {
     var selector = (thread_id == undefined)
         ? '.thread-body img[data-cid]'
-        : '.thread-body#thread-id-'+thread_id+' img[data-cid]';
+        : '.thread-body#thread-entry-'+thread_id+' img[data-cid]';
     $(selector).each(function(i, el) {
         var e = $(el),
             cid = e.data('cid').toLowerCase(),