diff --git a/reviewboard/static/rb/css/pages/diffviewer.less b/reviewboard/static/rb/css/pages/diffviewer.less
index c23c71699d2b3524df03e82a4e24517dcea70608..5b8c114b25ff3bd7548ba0d87c62077e28f2a057 100644
--- a/reviewboard/static/rb/css/pages/diffviewer.less
+++ b/reviewboard/static/rb/css/pages/diffviewer.less
@@ -3,6 +3,8 @@
 
 @img_base: '../../images';
 
+@commentflag-width: 1.6em;
+
 
 /*
  * The .diff-changes-* rules are used only within JavaScript code to
@@ -188,7 +190,7 @@
   tbody {
     tr {
       &.selected {
-        * {
+        > * {
           background: @diff-selected-color;
         }
 
@@ -358,7 +360,7 @@
 
     &.delete {
       tr {
-        &.selected * { background: @diff-delete-selected-color; }
+        &.selected > * { background: @diff-delete-selected-color; }
         &.highlight-anchor * { background: @diff-highlight-color; }
 
         td {
@@ -375,7 +377,7 @@
 
     &.insert {
       tr {
-        &.selected * { background: @diff-insert-selected-color; }
+        &.selected > * { background: @diff-insert-selected-color; }
         &.highlight-anchor * { background: @diff-highlight-color; }
 
         td {
@@ -392,7 +394,7 @@
 
     &.replace {
       tr {
-        &.selected * { background: @diff-replace-selected-color; }
+        &.selected > * { background: @diff-replace-selected-color; }
         &.highlight-anchor * { background: @diff-highlight-color; }
 
         td {
@@ -500,7 +502,7 @@
   left: -0.7em;
   margin-top: -0.3em;
   position: absolute;
-  width: 1.6em;
+  width: @commentflag-width;
 
   .commentflag-shadow {
     .border-radius(@comment-flag-border-radius);
@@ -512,7 +514,7 @@
     position: absolute;
     right: 0;
     top: 0.2em;
-    width: 100%;
+    width: @commentflag-width;
   }
 
   .commentflag-inner {
@@ -531,7 +533,7 @@
     text-align: center;
     top: 0;
     vertical-align: top;
-    width: 100%;
+    width: @commentflag-width / 0.9;
     z-index: @z-index-deco;
 
     span {
diff --git a/reviewboard/static/rb/js/views/textBasedCommentBlockView.js b/reviewboard/static/rb/js/views/textBasedCommentBlockView.js
index 0a7e15e9a34a409534ac1d04f6412f023e7e59d8..a38a9daf3a6dcf532db8b1ad37e9ab030ac8d1a5 100644
--- a/reviewboard/static/rb/js/views/textBasedCommentBlockView.js
+++ b/reviewboard/static/rb/js/views/textBasedCommentBlockView.js
@@ -9,8 +9,15 @@
  * This is meant to be used with a TextCommentBlock model.
  */
 RB.TextBasedCommentBlockView = RB.AbstractCommentBlockView.extend({
+    events: {
+        'click': '_onClicked',
+        'mouseover': '_onMouseOver',
+        'mouseleave': '_onMouseLeave'
+    },
+
     tagName: 'span',
     className: 'commentflag',
+    timeoutId: 'textBasedCommentBlockTimeoutId',
 
     template: _.template([
         '<span class="commentflag-shadow"></span>',
@@ -24,6 +31,8 @@ RB.TextBasedCommentBlockView = RB.AbstractCommentBlockView.extend({
      * Initializes the view.
      */
     initialize: function() {
+        _super(this).tooltipSides = 'rlbt';
+
         this.$beginRow = null;
         this.$endRow = null;
 
@@ -122,5 +131,89 @@ RB.TextBasedCommentBlockView = RB.AbstractCommentBlockView.extend({
                             this.$beginRow.offset().top -
                             (this.$el.getExtents('m', 't') || -4));
         }
+    },
+
+    /*
+     * Handler for when the comment block is clicked.
+     *
+     * Emits the 'clicked' signal so that parent views can process it.
+     */
+    _onClicked: function() {
+       this.trigger('clicked');
+    },
+
+    /*
+     * Handler for mouseover event.
+     *
+     * Spreads out the comment bubbles if they are overlapping.
+     */
+    _onMouseOver: function() {
+        var $parent = this.$el.parent(),
+            commentBubbles = $parent.find('.commentflag'),
+            initialWidth = this.$el.find('.commentflag-inner').width(),
+            initialLeft = commentBubbles.first().position().left,
+            spreadDistance = initialWidth + 3, /* Pixels between bubbles */
+            timeoutId = $parent.data(this.timeoutId);
+
+        clearTimeout(timeoutId);
+
+        /*
+         * timeoutId will only be defined during a mouseleave event, where the
+         * comment bubbles are spread out. It will be undefined when the
+         * comment bubbles are collapsed to the side.
+         */
+        if (commentBubbles.length !== 1 && typeof timeoutId === 'undefined') {
+            commentBubbles.each(_.bind(function(index, bubble) {
+                var leftMargin = initialLeft + spreadDistance * index,
+                    width = initialWidth + spreadDistance *
+                            (commentBubbles.length - index - 1);
+
+                this._shiftBubble(bubble, width, leftMargin);
+            }, this));
+        }
+    },
+
+    /*
+     * Handler for mouseleave event.
+     *
+     * Collapse comment bubbles to the side if the mouse is not hovering over
+     * them.
+     */
+    _onMouseLeave: function() {
+        var $parent = this.$el.parent(),
+            commentBubbles = $parent.find('.commentflag'),
+            initialWidth = this.$el.find('.commentflag-inner').width(),
+            initialLeft = commentBubbles.first().position().left;
+
+        /*
+         * Stores timeoutId to clear the timeout event if mouse is still
+         * hovering over the range of overlapping bubbles.
+         */
+        $parent.data(this.timeoutId, setTimeout(_.bind(function() {
+            if (commentBubbles.length != 1) {
+                commentBubbles.each(_.bind(function(index, bubble) {
+                    this._shiftBubble(bubble, initialWidth, initialLeft);
+                }, this));
+            }
+
+            /*
+             * Remove timeoutId data to represent that the range of
+             * overlapping bubbles are no longer spread out.
+             */
+            $parent.removeData(this.timeoutId)
+        }, this), 500));
+    },
+
+    /*
+     * Animates the expansion and collapse of the the overlapping comment
+     * bubbles.
+     */
+    _shiftBubble: function(bubble, width, left) {
+        $(bubble)
+            .css('width', width + 'px')
+            .stop()
+            .animate({
+                'left': left + 'px'
+            }, 500);
     }
 });
