diff --git a/opentech/apply/activity/messaging.py b/opentech/apply/activity/messaging.py
index edb540b2130358a560fcd71ed60f63730b332cca..4b07ca70384ee63ff023ffbfc7a6be86527439ac 100644
--- a/opentech/apply/activity/messaging.py
+++ b/opentech/apply/activity/messaging.py
@@ -1,11 +1,11 @@
-from enum import Enum
-
 import requests
 
+from django.db import models
 from django.conf import settings
 from django.contrib import messages
 from django.template.loader import render_to_string
 
+from .options import MESSAGES
 from .tasks import send_mail
 
 
@@ -13,27 +13,15 @@ def link_to(target, request):
     return request.scheme + '://' + request.get_host() + target.get_absolute_url()
 
 
-class MESSAGES(Enum):
-    UPDATE_LEAD = 'Update Lead'
-    EDIT = 'Edit'
-    APPLICANT_EDIT = "Applicant Edit"
-    NEW_SUBMISSION = 'New Submission'
-    TRANSITION = 'Transition'
-    DETERMINATION_OUTCOME = 'Determination Outcome'
-    INVITED_TO_PROPOSAL = 'Invited To Proposal'
-    REVIEWERS_UPDATED = 'Reviewers Updated'
-    READY_FOR_REVIEW = 'Ready For Review'
-    NEW_REVIEW = 'New Review'
-    COMMENT = 'Comment'
-    PROPOSAL_SUBMITTED = 'Proposal Submitted'
-    OPENED_SEALED = 'Opened Sealed Submission'
-
-    @classmethod
-    def choices(cls):
-        return [
-            (choice.name, choice.value)
-            for choice in cls
-        ]
+neat_related = {
+    MESSAGES.DETERMINATION_OUTCOME: 'determination',
+    MESSAGES.UPDATE_LEAD: 'old_lead',
+    MESSAGES.NEW_REVIEW: 'review',
+    MESSAGES.TRANSITION: 'old_phase',
+    MESSAGES.APPLICANT_EDIT: 'revision',
+    MESSAGES.EDIT: 'revision',
+    MESSAGES.COMMENT: 'comment',
+}
 
 
 class AdapterBase:
@@ -62,10 +50,32 @@ class AdapterBase:
     def extra_kwargs(self, message_type, **kwargs):
         return {}
 
+    def get_neat_related(self, message_type, related):
+        # We translate the related kwarg into something we can understand
+        try:
+            neat_name = neat_related[message_type]
+        except KeyError:
+            # Message type doesn't expect a related object
+            if related:
+                raise ValueError(f"Unexpected 'related' kwarg provided for {message_type}") from None
+            return {}
+        else:
+            if not related:
+                raise ValueError(f"{message_type} expects a 'related' kwarg")
+            return {neat_name: related}
+
     def recipients(self, message_type, **kwargs):
         raise NotImplementedError()
 
-    def process(self, message_type, event, **kwargs):
+    def process(self, message_type, event, request, user, submission, related=None, **kwargs):
+        kwargs = {
+            'request': request,
+            'user': user,
+            'submission': submission,
+            'related': related,
+            **kwargs,
+        }
+        kwargs.update(self.get_neat_related(message_type, related))
         kwargs.update(self.extra_kwargs(message_type, **kwargs))
 
         message = self.message(message_type, **kwargs)
@@ -86,7 +96,7 @@ class AdapterBase:
                     message = '{} [to: {}]: {}'.format(self.adapter_type, recipient, message)
                 else:
                     message = '{}: {}'.format(self.adapter_type, message)
-                messages.add_message(kwargs['request'], messages.INFO, message)
+                messages.add_message(request, messages.INFO, message)
 
     def create_log(self, message, recipient, event):
         from .models import Message
@@ -111,7 +121,7 @@ class ActivityAdapter(AdapterBase):
         MESSAGES.NEW_SUBMISSION: 'Submitted {submission.title} for {submission.page.title}',
         MESSAGES.EDIT: 'Edited',
         MESSAGES.APPLICANT_EDIT: 'Edited',
-        MESSAGES.UPDATE_LEAD: 'Lead changed from {old.lead} to {submission.lead}',
+        MESSAGES.UPDATE_LEAD: 'Lead changed from {old_lead} to {submission.lead}',
         MESSAGES.DETERMINATION_OUTCOME: 'Sent a determination. Outcome: {determination.clean_outcome}',
         MESSAGES.INVITED_TO_PROPOSAL: 'Invited to submit a proposal',
         MESSAGES.REVIEWERS_UPDATED: 'reviewers_updated',
@@ -123,7 +133,7 @@ class ActivityAdapter(AdapterBase):
         return [None]
 
     def extra_kwargs(self, message_type, **kwargs):
-        if message_type == MESSAGES.OPENED_SEALED:
+        if message_type in [MESSAGES.OPENED_SEALED, MESSAGES.REVIEWERS_UPDATED]:
             from .models import INTERNAL
             return {'visibility': INTERNAL}
         return {}
@@ -143,11 +153,19 @@ class ActivityAdapter(AdapterBase):
     def send_message(self, message, user, submission, **kwargs):
         from .models import Activity, PUBLIC
         visibility = kwargs.get('visibility', PUBLIC)
+
+        related = kwargs['related']
+        if isinstance(related, models.Model):
+            related_object = related
+        else:
+            related_object = None
+
         Activity.actions.create(
             user=user,
             submission=submission,
             message=message,
             visibility=visibility,
+            related_object=related_object,
         )
 
 
@@ -156,7 +174,7 @@ class SlackAdapter(AdapterBase):
     always_send = True
     messages = {
         MESSAGES.NEW_SUBMISSION: 'A new submission has been submitted for {submission.page.title}: <{link}|{submission.title}>',
-        MESSAGES.UPDATE_LEAD: 'The lead of <{link}|{submission.title}> has been updated from {old.lead} to {submission.lead} by {user}',
+        MESSAGES.UPDATE_LEAD: 'The lead of <{link}|{submission.title}> has been updated from {old_lead} to {submission.lead} by {user}',
         MESSAGES.COMMENT: 'A new comment has been posted on <{link}|{submission.title}>',
         MESSAGES.EDIT: '{user} has edited <{link}|{submission.title}>',
         MESSAGES.APPLICANT_EDIT: '{user} has edited <{link}|{submission.title}>',
@@ -286,14 +304,14 @@ class MessengerBackend:
     def __init__(self, *adpaters):
         self.adapters = adpaters
 
-    def __call__(self, message_type, request, user, submission, **kwargs):
-        return self.send(message_type, request=request, user=user, submission=submission, **kwargs)
+    def __call__(self, message_type, request, user, submission, related=None, **kwargs):
+        return self.send(message_type, request=request, user=user, submission=submission, related=related, **kwargs)
 
-    def send(self, message_type, user, submission, **kwargs):
+    def send(self, message_type, request, user, submission, related, **kwargs):
         from .models import Event
         event = Event.objects.create(type=message_type.name, by=user, submission=submission)
         for adapter in self.adapters:
-            adapter.process(message_type, event, user=user, submission=submission, **kwargs)
+            adapter.process(message_type, event, request=request, user=user, submission=submission, related=related, **kwargs)
 
 
 adapters = [
diff --git a/opentech/apply/activity/migrations/0012_add_generic_relation_to_activity.py b/opentech/apply/activity/migrations/0012_add_generic_relation_to_activity.py
new file mode 100644
index 0000000000000000000000000000000000000000..be9a21f8ab9e406dc485c86033e304fee85cdeb2
--- /dev/null
+++ b/opentech/apply/activity/migrations/0012_add_generic_relation_to_activity.py
@@ -0,0 +1,25 @@
+# Generated by Django 2.0.2 on 2018-09-06 09:43
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contenttypes', '0002_remove_content_type_name'),
+        ('activity', '0011_add_new_event_type'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='activity',
+            name='content_type',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
+        ),
+        migrations.AddField(
+            model_name='activity',
+            name='object_id',
+            field=models.PositiveIntegerField(blank=True, null=True),
+        ),
+    ]
diff --git a/opentech/apply/activity/models.py b/opentech/apply/activity/models.py
index 4ae6c0efc4b9d0969e0c16119e0f63196a5eb0e1..cabc295831be94bc29954fe864d51fe2e33e99ae 100644
--- a/opentech/apply/activity/models.py
+++ b/opentech/apply/activity/models.py
@@ -1,9 +1,11 @@
 from django.conf import settings
+from django.contrib.contenttypes.fields import GenericForeignKey
+from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.db.models import Case, When, Value
 from django.db.models.functions import Concat
 
-from .messaging import MESSAGES
+from .options import MESSAGES
 
 
 COMMENT = 'comment'
@@ -81,6 +83,11 @@ class Activity(models.Model):
     message = models.TextField()
     visibility = models.CharField(choices=VISIBILITY.items(), default=PUBLIC, max_length=10)
 
+    # Fields for generic relations to other objects. related_object should implement `get_absolute_url`
+    content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=models.CASCADE)
+    object_id = models.PositiveIntegerField(blank=True, null=True)
+    related_object = GenericForeignKey('content_type', 'object_id')
+
     objects = models.Manager.from_queryset(ActivityQuerySet)()
     comments = CommentManger.from_queryset(CommentQueryset)()
     actions = ActionManager.from_queryset(ActionQueryset)()
diff --git a/opentech/apply/activity/options.py b/opentech/apply/activity/options.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca9a78cb0baafbf87323780ab7f3b20e1971317f
--- /dev/null
+++ b/opentech/apply/activity/options.py
@@ -0,0 +1,24 @@
+from enum import Enum
+
+
+class MESSAGES(Enum):
+    UPDATE_LEAD = 'Update Lead'
+    EDIT = 'Edit'
+    APPLICANT_EDIT = "Applicant Edit"
+    NEW_SUBMISSION = 'New Submission'
+    TRANSITION = 'Transition'
+    DETERMINATION_OUTCOME = 'Determination Outcome'
+    INVITED_TO_PROPOSAL = 'Invited To Proposal'
+    REVIEWERS_UPDATED = 'Reviewers Updated'
+    READY_FOR_REVIEW = 'Ready For Review'
+    NEW_REVIEW = 'New Review'
+    COMMENT = 'Comment'
+    PROPOSAL_SUBMITTED = 'Proposal Submitted'
+    OPENED_SEALED = 'Opened Sealed Submission'
+
+    @classmethod
+    def choices(cls):
+        return [
+            (choice.name, choice.value)
+            for choice in cls
+        ]
diff --git a/opentech/apply/activity/templates/activity/include/listing_base.html b/opentech/apply/activity/templates/activity/include/listing_base.html
index dba0bc5b735d75d9f5ec1eb482a1ab0085636d5d..5f567ffe2f11e8ad7aba371ce7a75983009e8d4d 100644
--- a/opentech/apply/activity/templates/activity/include/listing_base.html
+++ b/opentech/apply/activity/templates/activity/include/listing_base.html
@@ -1,3 +1,4 @@
+{% load activity_tags %}
 <div class="feed__item feed__item--{{ activity.type }}">
     <div class="feed__pre-content">
         <p class="feed__label feed__label--{{ activity.type }}">{{ activity.type|capfirst }}</p>
@@ -14,13 +15,23 @@
             {% endif %}
         </div>
         <p class="feed__heading">
-            <span>{{ activity.user }}</span>
+            <span>{{ activity|display_author:request.user }}</span>
 
             {% if submission_title %}
                 updated <a href="{{ activity.submission.get_absolute_url }}">{{ activity.submission.title }}</a>
             {% endif %}
 
             - {{ activity.message }}
+
+            {% if not submission_title and activity|user_can_see_related:request.user %}
+                {% with url=activity.related_object.get_absolute_url %}
+                    {% if url %}
+                    <a href="{{ url }}" class="feed__related-item">
+                        <svg><use xlink:href="#arrow-head-pixels--solid"></use></svg>
+                    </a>
+                    {% endif %}
+                {% endwith %}
+            {% endif %}
         </p>
     </div>
 </div>
diff --git a/opentech/apply/activity/templatetags/__init__.py b/opentech/apply/activity/templatetags/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/opentech/apply/activity/templatetags/activity_tags.py b/opentech/apply/activity/templatetags/activity_tags.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f8c87ba50afb7cdca21112a8517701ebf75636f
--- /dev/null
+++ b/opentech/apply/activity/templatetags/activity_tags.py
@@ -0,0 +1,27 @@
+from django import template
+
+from opentech.apply.determinations.models import Determination
+from opentech.apply.review.models import Review
+
+register = template.Library()
+
+
+@register.filter
+def display_author(activity, user):
+    if user.is_applicant and isinstance(activity.related_object, Review):
+        return 'Reviewer'
+    return activity.user
+
+
+@register.filter
+def user_can_see_related(activity, user):
+    if not activity.related_object:
+        return False
+
+    if user.is_apply_staff:
+        return True
+
+    if isinstance(activity.related_object, Determination):
+        return True
+
+    return False
diff --git a/opentech/apply/activity/tests/test_messaging.py b/opentech/apply/activity/tests/test_messaging.py
index 77b3ca5924aa1f91523f6d970eb6301711355019..54576e91c79a8a363f03cb2ca41e38f0d260a1dc 100644
--- a/opentech/apply/activity/tests/test_messaging.py
+++ b/opentech/apply/activity/tests/test_messaging.py
@@ -19,6 +19,7 @@ from ..messaging import (
     ActivityAdapter,
     EmailAdapter,
     MessengerBackend,
+    neat_related,
     MESSAGES,
     SlackAdapter,
 )
@@ -46,18 +47,22 @@ class TestAdapter(AdapterBase):
 class AdapterMixin:
     adapter = None
 
-    def process_kwargs(self, **kwargs):
+    def process_kwargs(self, message_type, **kwargs):
         if 'user' not in kwargs:
             kwargs['user'] = UserFactory()
         if 'submission' not in kwargs:
             kwargs['submission'] = ApplicationSubmissionFactory()
         if 'request' not in kwargs:
             kwargs['request'] = make_request()
+        if message_type in neat_related:
+            kwargs['related'] = kwargs.get('related', 'a thing')
+        else:
+            kwargs['related'] = None
 
         return kwargs
 
     def adapter_process(self, message_type, **kwargs):
-        kwargs = self.process_kwargs(**kwargs)
+        kwargs = self.process_kwargs(message_type, **kwargs)
         self.adapter.process(message_type, event=EventFactory(submission=kwargs['submission']), **kwargs)
 
 
@@ -139,6 +144,7 @@ class TestMessageBackend(TestCase):
         self.mocked_adapter = Mock(AdapterBase)
         self.backend = MessengerBackend
         self.kwargs = {
+            'related': None,
             'request': None,
             'user': UserFactory(),
             'submission': ApplicationSubmissionFactory(),
@@ -185,7 +191,7 @@ class TestActivityAdapter(TestCase):
         user = UserFactory()
         submission = ApplicationSubmissionFactory()
 
-        self.adapter.send_message(message, user=user, submission=submission)
+        self.adapter.send_message(message, user=user, submission=submission, related=None)
 
         self.assertEqual(Activity.objects.count(), 1)
         activity = Activity.objects.first()
@@ -318,14 +324,14 @@ class TestEmailAdapter(AdapterMixin, TestCase):
     def test_no_email_private_comment(self):
         comment = CommentFactory(internal=True)
 
-        self.adapter_process(MESSAGES.COMMENT, comment=comment, submission=comment.submission)
+        self.adapter_process(MESSAGES.COMMENT, related=comment, submission=comment.submission)
         self.assertEqual(len(mail.outbox), 0)
 
     def test_no_email_own_comment(self):
         application = ApplicationSubmissionFactory()
         comment = CommentFactory(user=application.user, submission=application)
 
-        self.adapter_process(MESSAGES.COMMENT, comment=comment, user=comment.user, submission=comment.submission)
+        self.adapter_process(MESSAGES.COMMENT, related=comment, user=comment.user, submission=comment.submission)
         self.assertEqual(len(mail.outbox), 0)
 
     def test_reviewers_email(self):
diff --git a/opentech/apply/activity/views.py b/opentech/apply/activity/views.py
index ad90ea369daa8c50cb1d9b69468ea598887f894c..07ba2d1dcb64df46393c08315b44aaf410260ad0 100644
--- a/opentech/apply/activity/views.py
+++ b/opentech/apply/activity/views.py
@@ -34,9 +34,13 @@ class ActivityContextMixin:
         extra = {
             'actions': Activity.actions.filter(submission=self.object).select_related(
                 'user',
+            ).prefetch_related(
+                'related_object',
             ).visible_to(self.request.user),
             'comments': Activity.comments.filter(submission=self.object).select_related(
                 'user',
+            ).prefetch_related(
+                'related_object',
             ).visible_to(self.request.user),
         }
 
@@ -57,7 +61,7 @@ class CommentFormView(DelegatedViewMixin, CreateView):
             request=self.request,
             user=self.request.user,
             submission=self.object.submission,
-            comment=self.object,
+            related=self.object,
         )
         return response
 
diff --git a/opentech/apply/determinations/views.py b/opentech/apply/determinations/views.py
index 2225a20aed84cf1d0b2ef5777802bddbd1959d78..7be2e9a41722decac551578287d134b870e41975 100644
--- a/opentech/apply/determinations/views.py
+++ b/opentech/apply/determinations/views.py
@@ -92,7 +92,7 @@ class DeterminationCreateOrUpdateView(CreateOrUpdateView):
                 request=self.request,
                 user=self.object.author,
                 submission=self.object.submission,
-                determination=self.object,
+                related=self.object,
             )
             transition = transition_from_outcome(int(form.cleaned_data.get('outcome')), self.submission)
 
@@ -102,6 +102,7 @@ class DeterminationCreateOrUpdateView(CreateOrUpdateView):
                     message=self.object.stripped_message,
                     user=self.request.user,
                     submission=self.submission,
+                    related_object=self.object,
                 )
 
             self.submission.perform_transition(transition, self.request.user, request=self.request, notify=False)
diff --git a/opentech/apply/funds/models/submissions.py b/opentech/apply/funds/models/submissions.py
index eeaf49583146c37d79a50cace03b326e7b1091ff..93092d92110b0c9edf469a631209fccf53882d32 100644
--- a/opentech/apply/funds/models/submissions.py
+++ b/opentech/apply/funds/models/submissions.py
@@ -440,8 +440,8 @@ class ApplicationSubmission(
 
             self.draft_revision = revision
             self.save()
-            return True
-        return False
+            return revision
+        return None
 
     def clean_submission(self):
         self.process_form_data()
@@ -596,7 +596,7 @@ def log_status_update(sender, **kwargs):
             user=by,
             request=request,
             submission=instance,
-            old_phase=old_phase,
+            related=old_phase,
         )
 
         if instance.status in review_statuses:
@@ -635,3 +635,12 @@ class ApplicationRevision(AccessFormData, models.Model):
             'to': self.submission.live_revision.id,
             'from': self.id,
         })
+
+    def get_absolute_url(self):
+        # Compares against the previous revision
+        previous_revision = self.submission.revisions.filter(id__lt=self.id).first()
+        return reverse("funds:submissions:revisions:compare", kwargs={
+            'submission_pk': self.submission.id,
+            'to': self.id,
+            'from': previous_revision.id,
+        })
diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py
index eb27dc0dd2223153aea20c107748975e8a337ad7..8e614ba85c6ab824ced211c40325c1360571aa4f 100644
--- a/opentech/apply/funds/views.py
+++ b/opentech/apply/funds/views.py
@@ -104,7 +104,7 @@ class UpdateLeadView(DelegatedViewMixin, UpdateView):
             request=self.request,
             user=self.request.user,
             submission=form.instance,
-            old=old,
+            related=old.lead,
         )
         return response
 
@@ -280,13 +280,14 @@ class AdminSubmissionEditView(BaseSubmissionEditView):
             return self.form_invalid(form)
 
         if 'submit' in self.request.POST:
-            created = self.object.create_revision(by=self.request.user)
-            if created:
+            revision = self.object.create_revision(by=self.request.user)
+            if revision:
                 messenger(
                     MESSAGES.EDIT,
                     request=self.request,
                     user=self.request.user,
                     submission=self.object,
+                    related=revision,
                 )
 
         return HttpResponseRedirect(self.get_success_url())
@@ -315,7 +316,7 @@ class ApplicantSubmissionEditView(BaseSubmissionEditView):
             messages.success(self.request, _('Submission saved successfully'))
             return self.form_invalid(form)
 
-        created = self.object.create_revision(by=self.request.user)
+        revision = self.object.create_revision(by=self.request.user)
         submitting_proposal = self.object.phase.name in STAGE_CHANGE_ACTIONS
 
         if submitting_proposal:
@@ -325,12 +326,13 @@ class ApplicantSubmissionEditView(BaseSubmissionEditView):
                 user=self.request.user,
                 submission=self.object,
             )
-        elif created:
+        elif revision:
             messenger(
                 MESSAGES.APPLICANT_EDIT,
                 request=self.request,
                 user=self.request.user,
                 submission=self.object,
+                related=revision,
             )
 
         action = set(self.request.POST.keys()) & set(self.transitions.keys())
@@ -340,7 +342,7 @@ class ApplicantSubmissionEditView(BaseSubmissionEditView):
             transition.target,
             self.request.user,
             request=self.request,
-            notify=not (created or submitting_proposal),  # Use the other notification
+            notify=not (revision or submitting_proposal),  # Use the other notification
         )
 
         return HttpResponseRedirect(self.get_success_url())
diff --git a/opentech/apply/review/models.py b/opentech/apply/review/models.py
index 361a8d0c352070d509c80726df33cfd0badb48f9..75cfa7c1fad1fd8419eab33dbf4d45997db14aba 100644
--- a/opentech/apply/review/models.py
+++ b/opentech/apply/review/models.py
@@ -142,7 +142,7 @@ class Review(ReviewFormFieldsMixin, BaseStreamForm, AccessFormData, models.Model
         return '{:.1f}'.format(self.score) if self.score != NA else 'NA'
 
     def get_absolute_url(self):
-        return reverse('apply:reviews:review', args=(self.id,))
+        return reverse('apply:submissions:reviews:review', args=(self.submission.pk, self.id,))
 
     def __str__(self):
         return f'Review for {self.submission.title} by {self.author!s}'
diff --git a/opentech/apply/review/views.py b/opentech/apply/review/views.py
index 445efe2e4742f0c1e8c2442ea39b32c2e8aa34f5..70a3ad13ce6defe07672fec054c0c2630f393b40 100644
--- a/opentech/apply/review/views.py
+++ b/opentech/apply/review/views.py
@@ -89,7 +89,7 @@ class ReviewCreateOrUpdateView(BaseStreamForm, CreateOrUpdateView):
                 request=self.request,
                 user=self.object.author,
                 submission=self.submission,
-                review=self.object,
+                related=self.object,
             )
         return response
 
diff --git a/opentech/static_src/src/sass/apply/components/_feed.scss b/opentech/static_src/src/sass/apply/components/_feed.scss
index 71c84edeaeb1a669885c80498f7dc6238ca57def..8852c1926aa1ee515f7c210ff68fdecb2591e394 100644
--- a/opentech/static_src/src/sass/apply/components/_feed.scss
+++ b/opentech/static_src/src/sass/apply/components/_feed.scss
@@ -110,6 +110,16 @@
         }
     }
 
+    &__related-item {
+        svg {
+            width: 10px;
+            height: 14px;
+            margin-left: 10px;
+            margin-top: 0.35em;
+            fill: $color--dark-blue;
+        }
+    }
+
     &__heading {
         margin-bottom: 0;