diff --git a/scipost_django/common/constants.py b/scipost_django/common/constants.py
index 31499ef62d1adb514750e1ad2a0f8bf90395838f..e925a1d65bf953ad34bd28285d6b687dcb995e9f 100644
--- a/scipost_django/common/constants.py
+++ b/scipost_django/common/constants.py
@@ -17,72 +17,31 @@ CHARACTER_ALTERNATIVES = {
     "Ü": "Ue",
 }
 
-CHARACTER_UNACCENTED = {
-    "à": "a",
-    "À": "A",
-    "á": "a",
-    "Á": "A",
-    "â": "a",
-    "Â": "A",
-    "ä": "ae",
-    "Ä": "Ae",
-    "ã": "a",
-    "Ã": "A",
-    "Ã¥": "a",
-    "Ã…": "A",
-    "ç": "c",
-    "Ç": "C",
-    "ć": "c",
-    "Ć": "c",
-    "é": "e",
-    "É": "E",
-    "è": "e",
-    "È": "E",
-    "ê": "e",
-    "Ê": "E",
-    "ë": "e",
-    "Ë": "E",
-    "Ä™": "e",
-    "Ę": "E",
-    "í": "i",
-    "Í": "I",
-    "ì": "i",
-    "Ì": "I",
-    "î": "i",
-    "ÃŽ": "I",
-    "ï": "i",
-    "Ï": "I",
-    "Å‚": "l",
-    "Ł": "L",
-    "ñ": "n",
-    "Ñ": "N",
-    "Å„": "n",
-    "Ń": "N",
-    "ó": "o",
-    "Ó": "O",
-    "ò": "o",
-    "Ã’": "O",
-    "ô": "o",
-    "Ô": "O",
-    "ö": "oe",
-    "Ö": "Oe",
-    "õ": "o",
-    "Õ": "O",
+# Character latinisations are used to convert foreign letters
+# to their latinised equivalents / lookalikes.
+CHARACTER_LATINISATIONS = {
+    "æ": "ae",
+    "Æ": "Ae",
+    "Å“": "oe",
+    "Å’": "Oe",
+    "ß": "ss",
+    "ð": "d",
+    "Ð": "D",
     "ø": "o",
     "Ø": "O",
-    "Å›": "s",
-    "Åš": "S",
-    "ß": "ss",
-    "ú": "u",
-    "Ú": "U",
-    "ù": "u",
-    "Ù": "U",
-    "û": "u",
-    "Û": "U",
-    "ü": "ue",
-    "Ü": "Ue",
-    "ź": "z",
-    "Ź": "Z",
-    "ż": "z",
-    "Å»": "Z",
+    "Å‚": "l",
+    "Ł": "L",
+    "ij": "ij",
+    "IJ": "IJ",
+    "Å‹": "ng",
+    "ÅŠ": "Ng",
+    "ȶ": "t",
+    "È·": "j",
+    "ɉ": "j",
+    "É‹": "q",
+    "µ": "u",
+    "√": "v",
+    "á": "a",
+    "ą": "a",
+    "ı": "i",
 }
diff --git a/scipost_django/common/utils.py b/scipost_django/common/utils.py
index 5d3e35bd2c167aae79f70c2bad8ad110f62dfa7b..9e5b7fd34c56694cbd0d6bff988f7e0296134f81 100644
--- a/scipost_django/common/utils.py
+++ b/scipost_django/common/utils.py
@@ -9,17 +9,62 @@ from django.core.mail import EmailMultiAlternatives
 from django.db.models import Q
 from django.template import loader
 
-from .constants import CHARACTER_ALTERNATIVES, CHARACTER_UNACCENTED
+from .constants import CHARACTER_ALTERNATIVES, CHARACTER_LATINISATIONS
 
+import unicodedata
 
-def unaccent(text):
+
+def unaccent(text: str) -> str:
+    """
+    Remove accented characters in the given string (e.g. é -> e),
+    with the exception of the German umlauts (e.g. ö -> oe).
+    """
+    UMLAUT = "\u0308"
+
+    unaccented_text = ""
+    for char in unicodedata.normalize("NFD", text):
+        char_category = unicodedata.category(char)
+
+        if char_category != "Mn":
+            unaccented_text += char
+        elif char == UMLAUT:
+            unaccented_text += "e"
+
+    return unaccented_text
+
+
+def latinise(text: str) -> str:
     """
-    Replace accented characters by unaccented ones.
+    Convert accented characters in the given string to their
+    latinised equivalents / lookalikes (e.g. ö -> o).
     """
-    unaccented = text
-    for key, val in CHARACTER_UNACCENTED.items():
-        unaccented = unaccented.replace(key, val)
-    return unaccented
+    latinised_text = ""
+    for char in unicodedata.normalize("NFD", text):
+        char_category = unicodedata.category(char)
+
+        translated_char = char
+        is_latin = ord(char) < 128
+
+        # Keep spaces and dashes
+        if char in [" ", "-", "–"]:
+            pass
+        # Remove apostrophes and parentheses
+        elif char in ["'", "’", "(", ")"]:
+            translated_char = ""
+        # Translate only letters, symbols and punctuation
+        # skipping numbers and other characters (e.g. diacritics)
+        elif char_category[0] in ["L", "S", "P"] and not is_latin:
+            translated_char = CHARACTER_LATINISATIONS.get(char, "")
+
+        # Remove everything not in the ASCII range
+        translated_char = translated_char.encode("ascii", "ignore").decode("utf-8")
+
+        latinised_text += translated_char
+
+    # Remove multiple spaces
+    latinised_text = " ".join(latinised_text.split())
+
+    return latinised_text
 
 
 def alternative_spellings(text):
diff --git a/scipost_django/proceedings/forms.py b/scipost_django/proceedings/forms.py
index 17c0d8fcdceb6ebfe2aef528b05f666696d49532..0ab15c35344ab4656590d595338c97ea30d85021 100644
--- a/scipost_django/proceedings/forms.py
+++ b/scipost_django/proceedings/forms.py
@@ -23,8 +23,3 @@ class ProceedingsForm(forms.ModelForm):
             "submissions_close",
             "template_latex_tgz",
         )
-
-
-class ProceedingsMultipleChoiceField(forms.ModelMultipleChoiceField):
-    def label_from_instance(self, obj):
-        return obj.event_suffix or obj.event_name
diff --git a/scipost_django/production/admin.py b/scipost_django/production/admin.py
index 645fc77ac952b5335078de76f3639077e25a2bcb..191991474634f31e30458f52907baebabc549d61 100644
--- a/scipost_django/production/admin.py
+++ b/scipost_django/production/admin.py
@@ -15,6 +15,8 @@ from .models import (
     ProofsRepository,
 )
 
+from django.utils.html import format_html
+
 
 def event_count(obj):
     return obj.events.count()
@@ -102,11 +104,17 @@ class ProofsRepositoryAdmin(GuardedModelAdmin):
     search_fields = [
         "stream__submission__author_list",
         "stream__submission__title",
-        "stream__submission__preprint__identifier_w_vn_nr",
+        "name",
     ]
+
     list_filter = ["status"]
-    list_display = ["stream", "status", "git_path"]
-    readonly_fields = ["template_path", "git_path"]
+    list_display = ["name", "status", "gitlab_link"]
+    readonly_fields = ["stream", "template_path", "gitlab_link"]
+
+    def gitlab_link(self, obj):
+        return format_html(
+            '<a href="{1}" target="_blank">{0}</a>', obj.git_path, obj.git_url
+        )
 
 
 admin.site.register(ProofsRepository, ProofsRepositoryAdmin)
diff --git a/scipost_django/production/forms.py b/scipost_django/production/forms.py
index 44af1bec6e71cdcafa5fb6ef831d8e76ab60ed64..c944cc1f8cc1a837159c57021910cb0c7bcdacb4 100644
--- a/scipost_django/production/forms.py
+++ b/scipost_django/production/forms.py
@@ -3,11 +3,12 @@ __license__ = "AGPL v3"
 
 
 import datetime
+from typing import Dict
 
 from django import forms
 from django.contrib.auth import get_user_model
-from django.db.models import Max
-from django.db.models.functions import Greatest
+from django.db.models import Max, Value, Q
+from django.db.models.functions import Greatest, Coalesce, NullIf
 from django.contrib.sessions.backends.db import SessionStore
 
 from crispy_forms.helper import FormHelper
@@ -18,7 +19,6 @@ from django.urls import reverse
 from journals.models import Journal
 from markup.widgets import TextareaWithPreview
 from proceedings.models import Proceedings
-from proceedings.forms import ProceedingsMultipleChoiceField
 from scipost.fields import UserModelChoiceField
 
 from . import constants
@@ -281,36 +281,59 @@ class ProofsDecisionForm(forms.ModelForm):
 
 
 class ProductionStreamSearchForm(forms.Form):
-    accepted_in = forms.ModelMultipleChoiceField(
-        queryset=Journal.objects.active(),
+    author = forms.CharField(max_length=100, required=False, label="Author(s)")
+    title = forms.CharField(max_length=512, required=False)
+    identifier = forms.CharField(max_length=128, required=False)
+
+    all_streams = ProductionStream.objects.ongoing()
+    journal = forms.MultipleChoiceField(
+        choices=Journal.objects.active()
+        .filter(
+            id__in=all_streams.values_list(
+                "submission__editorialdecision__for_journal", flat=True
+            )
+        )
+        .order_by("name")
+        .values_list("id", "name"),
         required=False,
     )
-    proceedings = ProceedingsMultipleChoiceField(
-        queryset=Proceedings.objects.order_by("-submissions_close"),
+    proceedings = forms.MultipleChoiceField(
+        choices=Proceedings.objects.all()
+        .filter(id__in=all_streams.values_list("submission__proceedings", flat=True))
+        .order_by("-submissions_close")
+        # Short name is `event_suffix` if set, otherwise `event_name`
+        .annotate(short_name=Coalesce(NullIf("event_suffix", Value("")), "event_name"))
+        .values_list("id", "short_name")
+        .distinct(),
         required=False,
     )
-    author = forms.CharField(max_length=100, required=False, label="Author(s)")
-    title = forms.CharField(max_length=512, required=False)
-    identifier = forms.CharField(max_length=128, required=False)
-    officer = forms.ModelChoiceField(
-        queryset=ProductionUser.objects.active().filter(
-            user__groups__name="Production Officers"
-        ),
+    officer = forms.MultipleChoiceField(
+        choices=[(0, "Unassigned")]
+        + [
+            (prod_user.id, str(prod_user))
+            for prod_user in ProductionUser.objects.active()
+            .filter(id__in=all_streams.values_list("officer", flat=True))
+            .order_by("-user__id")
+            .distinct()
+        ],
         required=False,
-        empty_label="Any",
     )
-    supervisor = forms.ModelChoiceField(
-        queryset=ProductionUser.objects.active().filter(
-            user__groups__name="Production Supervisor"
-        ),
+    supervisor = forms.MultipleChoiceField(
+        choices=[(0, "Unassigned")]
+        + [
+            (prod_user.id, str(prod_user))
+            for prod_user in ProductionUser.objects.active()
+            .filter(id__in=all_streams.values_list("supervisor", flat=True))
+            .order_by("-user__id")
+            .distinct()
+        ],
         required=False,
-        empty_label="Any",
     )
     status = forms.MultipleChoiceField(
         # Use short status names from their internal (code) name
         choices=[
             (status_code_name, status_code_name.replace("_", " ").title())
-            for status_code_name, _ in constants.PRODUCTION_STREAM_STATUS
+            for status_code_name, _ in constants.PRODUCTION_STREAM_STATUS[:-2]
         ],
         required=False,
     )
@@ -352,39 +375,50 @@ class ProductionStreamSearchForm(forms.Form):
 
         self.helper = FormHelper()
         self.helper.layout = Layout(
-            Div(
-                Div(FloatingField("identifier"), css_class="col-md-3 col-4"),
-                Div(FloatingField("author"), css_class="col-md-3 col-8"),
-                Div(FloatingField("title"), css_class="col-md-6"),
-                css_class="row mb-0 mt-2",
-            ),
             Div(
                 Div(
                     Div(
-                        Div(Field("accepted_in", size=5), css_class="col-sm-8"),
-                        Div(Field("proceedings", size=5), css_class="col-sm-4"),
-                        css_class="row mb-0",
-                    ),
-                    Div(
-                        Div(Field("supervisor"), css_class="col-6"),
-                        Div(Field("officer"), css_class="col-6"),
+                        Div(FloatingField("identifier"), css_class="col-4"),
+                        Div(FloatingField("author"), css_class="col-8"),
+                        Div(FloatingField("title"), css_class="col-12"),
                         css_class="row mb-0",
                     ),
+                    css_class="col-12 col-md-8",
+                ),
+                Div(
                     Div(
-                        Div(Field("orderby"), css_class="col-6"),
-                        Div(Field("ordering"), css_class="col-6"),
+                        Div(FloatingField("orderby"), css_class="col-6 col-md-12"),
+                        Div(FloatingField("ordering"), css_class="col-6 col-md-12"),
                         css_class="row mb-0",
                     ),
-                    css_class="col-sm-9",
+                    css_class="col-12 col-md-4",
                 ),
+                css_class="row mb-0 mt-2",
+            ),
+            Div(
+                Div(Field("journal", size=10), css_class="col-6 col-md-4 col-lg"),
                 Div(
-                    Field("status", size=len(constants.PRODUCTION_STREAM_STATUS)),
-                    css_class="col-sm-3",
+                    Field("proceedings", size=10),
+                    css_class="col-6 col-md-8 col-lg d-none d-md-block",
                 ),
+                Div(Field("status", size=10), css_class="col-6 col-md-4 col-lg"),
+                Div(Field("supervisor", size=10), css_class="col-6 col-md-4 col-lg"),
+                Div(Field("officer", size=10), css_class="col-6 col-md-4 col-lg"),
                 css_class="row mb-0",
             ),
         )
 
+    def apply_filter_set(self, filters: Dict, none_on_empty: bool = False):
+        # Apply the filter set to the form
+        for key in self.fields:
+            if key in filters:
+                self.fields[key].initial = filters[key]
+            elif none_on_empty:
+                if isinstance(self.fields[key], forms.MultipleChoiceField):
+                    self.fields[key].initial = []
+                else:
+                    self.fields[key].initial = None
+
     def search_results(self):
         # Save the form data to the session
         if self.session_key is not None:
@@ -393,23 +427,6 @@ class ProductionStreamSearchForm(forms.Form):
             for key in self.cleaned_data:
                 session[key] = self.cleaned_data.get(key)
 
-            session["accepted_in"] = (
-                [journal.id for journal in session.get("accepted_in")]
-                if (session.get("accepted_in"))
-                else []
-            )
-            session["proceedings"] = (
-                [proceedings.id for proceedings in session.get("proceedings")]
-                if (session.get("proceedings"))
-                else []
-            )
-            session["officer"] = (
-                officer.id if (officer := session.get("officer")) else None
-            )
-            session["supervisor"] = (
-                supervisor.id if (supervisor := session.get("supervisor")) else None
-            )
-
             session.save()
 
         streams = ProductionStream.objects.ongoing()
@@ -418,13 +435,6 @@ class ProductionStreamSearchForm(forms.Form):
             latest_activity_annot=Greatest(Max("events__noted_on"), "opened", "closed")
         )
 
-        if accepted_in := self.cleaned_data.get("accepted_in"):
-            streams = streams.filter(
-                submission__editorialdecision__for_journal__in=accepted_in,
-            )
-        if proceedings := self.cleaned_data.get("proceedings"):
-            streams = streams.filter(submission__proceedings__in=proceedings)
-
         if identifier := self.cleaned_data.get("identifier"):
             streams = streams.filter(
                 submission__preprint__identifier_w_vn_nr__icontains=identifier,
@@ -434,12 +444,31 @@ class ProductionStreamSearchForm(forms.Form):
         if title := self.cleaned_data.get("title"):
             streams = streams.filter(submission__title__icontains=title)
 
-        if officer := self.cleaned_data.get("officer"):
-            streams = streams.filter(officer=officer)
-        if supervisor := self.cleaned_data.get("supervisor"):
-            streams = streams.filter(supervisor=supervisor)
-        if status := self.cleaned_data.get("status"):
-            streams = streams.filter(status__in=status)
+        def is_in_or_null(queryset, key, value, implicit_all=True):
+            """
+            Filter a queryset by a list of values. If the list contains a 0, then
+            also include objects where the key is null. If the list is empty, then
+            include all objects if implicit_all is True.
+            """
+            value = self.cleaned_data.get(value)
+            has_unassigned = "0" in value
+            is_unassigned = Q(**{key + "__isnull": True})
+            is_in_values = Q(**{key + "__in": list(filter(lambda x: x != 0, value))})
+
+            if has_unassigned:
+                return queryset.filter(is_unassigned | is_in_values)
+            elif implicit_all and not value:
+                return queryset
+            else:
+                return queryset.filter(is_in_values)
+
+        streams = is_in_or_null(
+            streams, "submission__editorialdecision__for_journal", "journal"
+        )
+        streams = is_in_or_null(streams, "submission__proceedings", "proceedings")
+        streams = is_in_or_null(streams, "officer", "officer")
+        streams = is_in_or_null(streams, "supervisor", "supervisor")
+        streams = is_in_or_null(streams, "status", "status")
 
         if not self.user.has_perm("scipost.can_view_all_production_streams"):
             # Restrict stream queryset if user is not supervisor
diff --git a/scipost_django/production/management/commands/advance_git_repos.py b/scipost_django/production/management/commands/advance_git_repos.py
index c9640dfa9a3068a82b8b5c5e3ad888ea8ddc2e5c..4c93d9a3d62dd09ab9f43bcb2fb1a1b6473c5117 100644
--- a/scipost_django/production/management/commands/advance_git_repos.py
+++ b/scipost_django/production/management/commands/advance_git_repos.py
@@ -13,6 +13,7 @@ from common.utils import get_current_domain
 from gitlab import Gitlab
 from gitlab.v4.objects import Group, Project
 from gitlab.exceptions import GitlabGetError
+from gitlab.const import AccessLevel
 
 import arxiv
 import requests
@@ -42,7 +43,7 @@ class Command(BaseCommand):
     def add_arguments(self, parser: CommandParser) -> None:
         parser.add_argument(
             "--id",
-            type=int,
+            type=str,
             required=False,
             help="The submission preprint identifier to handle a specific submission, leave blank to handle all",
         )
@@ -197,6 +198,19 @@ class Command(BaseCommand):
             }
         )
 
+        # Allow Developers to push to the protected "main" branch
+        # Protected branches lay on top of the branches. Deleting and recreating them is
+        # the only way to change their settings and does not affect the branches themselves
+        project.protectedbranches.delete("main")
+        project.protectedbranches.create(
+            {
+                "name": "main",
+                "merge_access_level": AccessLevel.MAINTAINER,
+                "push_access_level": AccessLevel.DEVELOPER,
+                "allow_force_push": False,
+            }
+        )
+
         self.stdout.write(
             self.style.SUCCESS(f"Copied pure templates to {repo.git_path}")
         )
@@ -241,12 +255,18 @@ class Command(BaseCommand):
 
         # Define the formatting functions
         def format_authors(authors: List[str]) -> str:
+            # Append a superscript to each author
+            authors = [
+                author + "\\textsuperscript{" + str(i) + "}"
+                for i, author in enumerate(authors, start=1)
+            ]
+
             *other_authors, last_author = authors
 
             if len(other_authors) == 0:
                 return last_author
             else:
-                return ", ".join(other_authors) + " and " + last_author
+                return ",\n".join(other_authors) + "\nand " + last_author
 
         def format_title(title: str) -> str:
             return title + NEWLINE
diff --git a/scipost_django/production/migrations/0007_auto_20230706_1502.py b/scipost_django/production/migrations/0007_auto_20230706_1502.py
new file mode 100644
index 0000000000000000000000000000000000000000..347c2862fdd26f8de996c832f7350d2978878a78
--- /dev/null
+++ b/scipost_django/production/migrations/0007_auto_20230706_1502.py
@@ -0,0 +1,67 @@
+# Generated by Django 3.2.18 on 2023-07-06 13:02
+
+from django.db import migrations, models
+
+
+def add_name_to_repos(apps, schema_editor):
+    from common.utils import latinise
+    from django.db.models.functions import Concat
+    from django.db.models import Value
+
+    ProofsRepository = apps.get_model("production", "ProofsRepository")
+    Profile = apps.get_model("profiles", "Profile")
+
+    def _clean_author_list(authors_str: str):
+        comma_separated = authors_str.replace(", and", ", ")
+        comma_separated = comma_separated.replace(" and ", ", ")
+        comma_separated = comma_separated.replace(", & ", ", ")
+        comma_separated = comma_separated.replace(" & ", ", ")
+        comma_separated = comma_separated.replace(";", ", ")
+        return [e.lstrip().rstrip() for e in comma_separated.split(",")]
+
+    def _get_repo_name(stream) -> str:
+        """
+        Return the name of the repository in the form of "id_lastname".
+        """
+        # Get the last name of the first author by getting the first author string from the submission
+        first_author_str = _clean_author_list(stream.submission.author_list)[0]
+        first_author_profile = (
+            Profile.objects.annotate(
+                full_name=Concat("first_name", Value(" "), "last_name")
+            )
+            .filter(full_name=first_author_str)
+            .first()
+        )
+        if first_author_profile is None:
+            first_author_last_name = first_author_str.split(" ")[-1]
+        else:
+            first_author_last_name = first_author_profile.last_name
+
+        # Remove accents from the last name to avoid encoding issues
+        # and join multiple last names into one
+        first_author_last_name = latinise(first_author_last_name).strip()
+        first_author_last_name = first_author_last_name.replace(" ", "-")
+
+        return "{preprint_id}_{last_name}".format(
+            preprint_id=stream.submission.preprint.identifier_w_vn_nr,
+            last_name=first_author_last_name,
+        )
+
+    for repo in ProofsRepository.objects.all():
+        repo.name = _get_repo_name(repo.stream)
+        repo.save()
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("production", "0006_proofsrepository"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="proofsrepository",
+            name="name",
+            field=models.CharField(default="", max_length=128),
+        ),
+        migrations.RunPython(add_name_to_repos, reverse_code=migrations.RunPython.noop),
+    ]
diff --git a/scipost_django/production/migrations/0008_alter_productionstream_status.py b/scipost_django/production/migrations/0008_alter_productionstream_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a396621394e742116c79b25ee5dd2100b2d8c5b
--- /dev/null
+++ b/scipost_django/production/migrations/0008_alter_productionstream_status.py
@@ -0,0 +1,34 @@
+# Generated by Django 3.2.18 on 2023-07-06 14:01
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("production", "0007_auto_20230706_1502"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="productionstream",
+            name="status",
+            field=models.CharField(
+                choices=[
+                    ("initiated", "New Stream started"),
+                    ("source_requested", "Source files requested"),
+                    ("tasked", "Supervisor tasked officer with proofs production"),
+                    ("produced", "Proofs have been produced"),
+                    ("checked", "Proofs have been checked by Supervisor"),
+                    ("sent", "Proofs sent to Authors"),
+                    ("returned", "Proofs returned by Authors"),
+                    ("corrected", "Corrections implemented"),
+                    ("accepted", "Authors have accepted proofs"),
+                    ("published", "Paper has been published"),
+                    ("cited", "Cited people have been notified/invited to SciPost"),
+                    ("completed", "Completed"),
+                ],
+                default="initiated",
+                max_length=32,
+            ),
+        ),
+    ]
diff --git a/scipost_django/production/models.py b/scipost_django/production/models.py
index 55023d88c41ae8ba26ab1e94985dcd628fdd3e3e..1e52ef4ebe02072bcd3e8daf6ec7db92983b098f 100644
--- a/scipost_django/production/models.py
+++ b/scipost_django/production/models.py
@@ -15,6 +15,8 @@ from django.db.models import Value
 from django.db.models.functions import Concat
 from django.conf import settings
 
+from common.utils import latinise
+
 from .constants import (
     PRODUCTION_STREAM_STATUS,
     PRODUCTION_STREAM_INITIATED,
@@ -293,14 +295,18 @@ class ProofsRepository(models.Model):
         choices=PROOFS_REPO_STATUSES,
         default=PROOFS_REPO_UNINITIALIZED,
     )
+    name = models.CharField(max_length=128, default="")
 
-    @property
-    def name(self) -> str:
+    def __str__(self):
+        return self.name
+
+    @staticmethod
+    def _get_repo_name(stream) -> str:
         """
         Return the name of the repository in the form of "id_lastname".
         """
         # Get the last name of the first author by getting the first author string from the submission
-        first_author_str = self.stream.submission.authors_as_list[0]
+        first_author_str = stream.submission.authors_as_list[0]
         first_author_profile = (
             Profile.objects.annotate(
                 full_name=Concat("first_name", Value(" "), "last_name")
@@ -312,11 +318,14 @@ class ProofsRepository(models.Model):
             first_author_last_name = first_author_str.split(" ")[-1]
         else:
             first_author_last_name = first_author_profile.last_name
-            # Keep only the last of the last names
-            first_author_last_name = first_author_last_name.split(" ")[-1]
+
+        # Remove accents from the last name to avoid encoding issues
+        # and join multiple last names into one
+        first_author_last_name = latinise(first_author_last_name).strip()
+        first_author_last_name = first_author_last_name.replace(" ", "-")
 
         return "{preprint_id}_{last_name}".format(
-            preprint_id=self.stream.submission.preprint.identifier_w_vn_nr,
+            preprint_id=stream.submission.preprint.identifier_w_vn_nr,
             last_name=first_author_last_name,
         )
 
@@ -409,6 +418,7 @@ def production_stream_create_proofs_repo(sender, instance, created, **kwargs):
         ProofsRepository.objects.create(
             stream=instance,
             status=ProofsRepository.PROOFS_REPO_UNINITIALIZED,
+            name=ProofsRepository._get_repo_name(instance),
         )
 
 
diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html b/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html
index ac9e3efc8ba0ad5ddb719dbb080aa0fda5cb936f..8152c57481fd30234e3a95cd96c3e064a26bdd28 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html
@@ -2,7 +2,7 @@
 
 {% if form.fields.invitations_officer.choices|length > 0 %}
     <div id="productionstream-{{ stream.id }}-update-invitations_officer">
-        <form hx-post="{% url 'production:update_invitations_officer' stream.id %}"
+        <form hx-post="{% url 'production:_hx_update_invitations_officer' stream.id %}"
               hx-target="#productionstream-{{ stream.id }}-update-invitations_officer"
               hx-swap="outerHTML"
               class="row mb-0">
diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_officer.html b/scipost_django/production/templates/production/_hx_productionstream_change_officer.html
index 2efbd7f2ebb9f75e1f2e64f85e11ed8f0248086d..014d8347a20bd1184865ce74b261861019455a66 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_change_officer.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_officer.html
@@ -2,7 +2,7 @@
 
 {% if form.fields.officer.choices|length > 0 %}
     <div id="productionstream-{{ stream.id }}-update-officer">
-        <form hx-post="{% url 'production:update_officer' stream.id %}"
+        <form hx-post="{% url 'production:_hx_update_officer' stream.id %}"
               hx-target="#productionstream-{{ stream.id }}-update-officer"
               hx-swap="outerHTML"
               class="row">
diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_status.html b/scipost_django/production/templates/production/_hx_productionstream_change_status.html
index 874111480ad50645cf1518a02f55608d585e0069..e2358f8ed2ac967612f05ed38c67dab6a3783296 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_change_status.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_status.html
@@ -2,7 +2,7 @@
 
 {% if form.fields.status.choices|length > 0 %}
     <form id="productionstream-{{ stream.id }}-update-status"
-          hx-post="{% url 'production:update_status' stream.id %}"
+          hx-post="{% url 'production:_hx_update_status' stream.id %}"
           hx-target="this"
           hx-swap="outerHTML"
           hx-trigger="submit"
diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html b/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html
index 4ce3afd863abb8ac1a9ece6f79340083938592c1..2a0aba9c9afb93535c0230dc98aa0366be4ecf3e 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html
@@ -2,7 +2,7 @@
 
 {% if form.fields.supervisor.choices|length > 0 %}
     <div id="productionstream-{{ stream.id }}-update-supervisor">
-        <form hx-post="{% url 'production:update_supervisor' stream.id %}"
+        <form hx-post="{% url 'production:_hx_update_supervisor' stream.id %}"
               hx-target="#productionstream-{{ stream.id }}-update-supervisor"
               hx-swap="outerHTML"
               class="row">
diff --git a/scipost_django/production/templates/production/_hx_productionstream_details.html b/scipost_django/production/templates/production/_hx_productionstream_details.html
index 2e51ab633cda7337e6d7b40ba0bcd0f0b1c63399..a4977aae49aae0ab3e6416421763c205a13b2c18 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_details.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_details.html
@@ -1,12 +1,12 @@
 <details id="productionstream-{{ productionstream.id }}-details"
          class="border border-2 mx-3 p-2 bg-primary bg-opacity-10">
-  <summary class="summary-unstyled p-2">
+  <summary class="list-none">
     {% include "production/_productionstream_details_summary_contents.html" with productionstream=productionstream %}
   </summary>
 
 
   <div id="productionstream-{{ productionstream.id }}-details-contents"
-       class="m-2 p-2 bg-white"
+       class="p-2 mt-2 bg-white"
        hx-get="{% url 'production:_hx_productionstream_details_contents' productionstream_id=productionstream.id %}"
        hx-trigger="toggle once from:#productionstream-{{ productionstream.id }}-details"
        hx-indicator="#indicator-productionstream-{{ productionstream.id }}-details-contents"></div>
diff --git a/scipost_django/production/templates/production/_hx_productionstream_details_contents.html b/scipost_django/production/templates/production/_hx_productionstream_details_contents.html
index 2fdf85f4ffcb53b7ad0a3267ed5329cd068435e0..476f155e9bb58018655ec51545291e1433e249ed 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_details_contents.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_details_contents.html
@@ -4,11 +4,11 @@
 
 {% get_obj_perms request.user for productionstream as "sub_perms" %}
 
-<div class="row">
+<div class="row mb-0">
   {% if "can_work_for_stream" in sub_perms or perms.scipost.can_assign_production_supervisor %}
     <div class="col-12 col-md d-flex flex-column">
  
-      <div class="accordion px-2 mb-2"
+      <div class="accordion"
            id="productionstream-{{ productionstream.id }}-actions-accordion">
         <h3>Actions</h3>
 
@@ -74,7 +74,7 @@
                  data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion">
               <div id="productionstream-{{ productionstream.id }}-upload-proofs-body"
                    class="accordion-body"
-                   hx-get="{% url 'production:upload_proofs' stream_id=productionstream.id %}"
+                   hx-get="{% url 'production:_hx_upload_proofs' stream_id=productionstream.id %}"
                    hx-trigger="intersect once">
  
                 {% comment %} Placeholder before HTMX content loads {% endcomment %}
@@ -170,13 +170,13 @@
       {% if perms.scipost.can_draft_publication or perms.scipost.can_publish_accepted_submission %}
         <div class="mb-2 mb-md-0 mt-md-auto px-2">
  
-          <div class="row mb-0 g-2">
+          <div class="row mb-0 mt-2 g-2">
             {% if perms.scipost.can_publish_accepted_submission %}
               <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty">
                 <div class="row m-0 d-none-empty">
                   <button class="btn btn-warning text-white"
-                          hx-get="{% url 'production:mark_as_completed' stream_id=productionstream.id %}"
-                          {% if stream.status != 'published' %}hx-confirm="Are you sure you want to mark this unpublished stream as completed?"{% endif %}
+                          hx-get="{% url 'production:_hx_mark_as_completed' stream_id=productionstream.id %}"
+                          {% if productionstream.status != 'published' %}hx-confirm="Are you sure you want to mark this unpublished stream as completed?"{% endif %}
                           hx-target="#productionstream-{{ productionstream.id }}-details">
                     Mark this stream as completed
                   </button>
@@ -184,7 +184,7 @@
               </div>
             {% endif %}
  
-            {% if perms.scipost.can_draft_publication and stream.status == 'accepted' %}
+            {% if perms.scipost.can_draft_publication and productionstream.status == 'accepted' %}
               <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty">
                 <div class="row m-0 d-none-empty">
                   <a class="btn btn-primary text-white"
@@ -202,7 +202,7 @@
   </div>
 
   <div id="productionstream-{{ productionstream.id }}-event-container"
-       class="col-12 col-md d-flex flex-column px-3">
+       class="col-12 col-md d-flex flex-column">
     {% comment %} This might be better to refactor with an OOB response on each event addition {% endcomment %}
     <h3>Events</h3>
     <div id="productionstream-{{ productionstream.id }}-event-list"
diff --git a/scipost_django/production/templates/production/_hx_productionstream_search_form.html b/scipost_django/production/templates/production/_hx_productionstream_search_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..177079afdf6d853d4465550126697f6b71977160
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_search_form.html
@@ -0,0 +1,10 @@
+{% load crispy_forms_tags %}
+
+<form hx-post="{% url 'production:_hx_productionstream_list' %}"
+      hx-trigger="load, keyup delay:500ms, change delay:500ms, click from:#refresh-button"
+      hx-sync="#search-productionstreams-form:replace"
+      hx-target="#search-productionstreams-results"
+      hx-indicator="#indicator-search-productionstreams">
+ 
+  <div id="search-productionstreams-form">{% crispy form %}</div>
+</form>
diff --git a/scipost_django/production/templates/production/_hx_upload_proofs.html b/scipost_django/production/templates/production/_hx_upload_proofs.html
new file mode 100644
index 0000000000000000000000000000000000000000..a60d1c2c6d8dc18ed091141455fd55e6b594d9a2
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_upload_proofs.html
@@ -0,0 +1,27 @@
+{% load bootstrap %}
+
+
+<h3>Proofs</h3>
+<div class="accordion"
+     id="productionstream-{{ stream.id }}-proofs-list-accordion">
+  {% for proofs in stream.proofs.all %}
+    {% include 'production/_hx_productionstream_actions_proofs_item.html' with i_proof=forloop.counter0|add:1 active_id=total_proofs stream=stream proofs=proofs %}
+  {% empty %}
+    <div>No Proofs found.</div>
+  {% endfor %}
+</div>
+
+<div class="row mt-3">
+  <div class="col-12">
+    <form enctype="multipart/form-data"
+          hx-post="{% url 'production:_hx_upload_proofs' stream_id=stream.id %}"
+          hx-target="#productionstream-{{ stream.id }}-upload-proofs-body">
+      {% csrf_token %}
+      {{ form|bootstrap_purely_inline }}
+      <input type="submit"
+             class="btn btn-primary proof-action-button"
+             name="submit"
+             value="Upload">
+    </form>
+  </div>
+</div>
diff --git a/scipost_django/production/templates/production/_production_stream_card.html b/scipost_django/production/templates/production/_production_stream_card.html
index a7fbb4c1edbb1ae01a67d828c1bc3bc699999999..2b4430a24ae092adb1c7a641d336dffe57d07f0e 100644
--- a/scipost_django/production/templates/production/_production_stream_card.html
+++ b/scipost_django/production/templates/production/_production_stream_card.html
@@ -88,7 +88,7 @@
           <li>
             <button type="button" class="btn btn-link" data-bs-toggle="toggle" data-bs-target="#upload_proofs">Upload Proofs</button>
             <div id="upload_proofs" style="display: none;">
-              <form class="my-3" action="{% url 'production:upload_proofs' stream_id=stream.id %}" method="post" enctype="multipart/form-data">
+              <form class="my-3" action="{% url 'production:_hx_upload_proofs' stream_id=stream.id %}" method="post" enctype="multipart/form-data">
                 {% csrf_token %}
                 {{ upload_proofs_form|bootstrap_inline }}
                 <input type="submit" class="btn btn-outline-primary" name="submit" value="Upload">
diff --git a/scipost_django/production/templates/production/_productionstream_details_summary_contents.html b/scipost_django/production/templates/production/_productionstream_details_summary_contents.html
index 6fb15371a18c733794117ae31a8497750b3aab03..7239595fcf5f72d457fbb1b1137398b778c26e38 100644
--- a/scipost_django/production/templates/production/_productionstream_details_summary_contents.html
+++ b/scipost_django/production/templates/production/_productionstream_details_summary_contents.html
@@ -64,12 +64,11 @@
                 <small class="text-muted">Submitter</small>
                 <br>
                 {% if productionstream.submission.submitted_by.profile.email %}
-                    <a href="mailto:{{ productionstream.submission.submitted_by.profile.email }}?body=Dear%20{{ productionstream.submission.submitted_by.formal_str }},%0A%0A">
-                        {{ productionstream.submission.submitted_by.formal_str }}
-                    </a>
-                {% else %}
-                    {{ productionstream.submission.submitted_by.formal_str }}
+                    <a href="mailto:{{ productionstream.submission.submitted_by.profile.email }}?body=Dear%20{{ productionstream.submission.submitted_by.formal_str }},%0A%0A"
+                       class="text-primary"><span style="pointer-events: none;">{% include 'bi/pencil-square.html' %}</span></a>
+                    &nbsp;
                 {% endif %}
+                <a href="{% url 'scipost:contributor_info' productionstream.submission.submitted_by.id %}">{{ productionstream.submission.submitted_by.formal_str }}</a>
             </div>
 
             <div class="col-auto">
@@ -77,6 +76,8 @@
                 <br>
                 <div class="d-inline-flex">
                     <a href="{{ productionstream.submission.get_absolute_url }}">Submission</a>
+                    &nbsp;&nbsp; &bullet; &nbsp;&nbsp;
+                    <a href="{% url 'production:stream' stream_id=productionstream.id %}">Stream</a>
                     {% if perms.scipost.can_oversee_refereeing %}
                         &nbsp;&nbsp; &bullet; &nbsp;&nbsp;
                         <a href="{% url 'submissions:editorial_page' productionstream.submission.preprint.identifier_w_vn_nr %}">Editorial</a>
diff --git a/scipost_django/production/templates/production/_productionstream_events.html b/scipost_django/production/templates/production/_productionstream_events.html
index 28b1ed7d2fb9dadef06ba3d8d6142cfe57d03147..03918572a39b555c95fa419d05da8dd739d24842 100644
--- a/scipost_django/production/templates/production/_productionstream_events.html
+++ b/scipost_django/production/templates/production/_productionstream_events.html
@@ -50,15 +50,15 @@
       </tr>
       <tr>
 	
-        <td colspan="4" class="ps-4 pb-4">
+        <td colspan="4" class="py-1 px-3">
           {% if event.comments %}
-            <p class="mt-2 mb-0">
+            <div>
               {% if event.noted_to %}
                 <strong>To: {{ event.noted_to.user.first_name }} {{ event.noted_to.user.last_name }}</strong>
                 <br>
               {% endif %}
               {% automarkup event.comments %}
-            </p>
+            </div>
           {% endif %}
 
  
diff --git a/scipost_django/production/templates/production/production_new.html b/scipost_django/production/templates/production/production_new.html
index 388326ba4d72a5a47cc83c2762a180b4e5d23279..6f2c4b9bad5602a074e658aa8c0590a7bef9894b 100644
--- a/scipost_django/production/templates/production/production_new.html
+++ b/scipost_django/production/templates/production/production_new.html
@@ -25,11 +25,11 @@
   </div>
 
   <details id="productionstreams-filter-details" class="card my-4">
-    <summary class="card-header fs-6 d-inline-flex align-items-center">
-      Search / Filter / Bulk Actions
-      <div class="d-none d-sm-inline-flex ms-auto align-items-center">
+    <summary class="card-header fs-6 d-flex flex-row align-items-center justify-content-between list-triangle">
+      <div>Search / Filter / Bulk Actions</div>
+      <div class="d-none d-md-flex align-items-center">
+ 
         <div id="indicator-search-productionstreams" class="htmx-indicator">
-	
           <button class="btn btn-warning text-white d-none d-md-block me-2"
                   type="button"
                   disabled>
@@ -38,10 +38,14 @@
             <div class="spinner-grow spinner-grow-sm ms-2"
                  role="status"
                  aria-hidden="true"></div>
- 
           </button>
         </div>
 
+        <button class="btn btn-outline-secondary me-2"
+                type="button"
+                hx-get="{% url 'production:_hx_productionstream_search_form' filter_set="empty" %}"
+                hx-target="#productionstream-search-form-container">Clear Filters</button>
+ 
         <a id="refresh-button" class="m-2 btn btn-primary">
           {% include "bi/arrow-clockwise.html" %}
         &nbsp;Refresh</a>
@@ -49,13 +53,9 @@
 
     </summary>
     <div class="card-body">
-      <form hx-post="{% url 'production:_hx_productionstream_list' %}"
-            hx-trigger="load, keyup delay:500ms, change, click from:#refresh-button"
-            hx-target="#search-productionstreams-results"
-            hx-indicator="#indicator-search-productionstreams">
-	
-        <div id="search-productionstreams-form">{% crispy search_productionstreams_form %}</div>
-      </form>
+      <div id="productionstream-search-form-container">
+        {% include 'production/_hx_productionstream_search_form.html' with form=search_productionstreams_form %}
+      </div>
 
       {% comment %} Bulk Action buttons {% endcomment %}
 
diff --git a/scipost_django/production/templates/production/upload_proofs.html b/scipost_django/production/templates/production/upload_proofs.html
index afa710d9b9a743eb84cffd2967076dac34d7b02a..16c6c905ebab596a3884f0665cd1cec92792efcf 100644
--- a/scipost_django/production/templates/production/upload_proofs.html
+++ b/scipost_django/production/templates/production/upload_proofs.html
@@ -1,27 +1,32 @@
-{% load bootstrap %}
+{% extends 'production/base.html' %}
 
+{% block breadcrumb_items %}
+  {{ block.super }}
+  <span class="breadcrumb-item">Upload Proofs</span>
+{% endblock %}
 
-<h3>Proofs</h3>
-<div class="accordion"
-     id="productionstream-{{ stream.id }}-proofs-list-accordion">
-  {% for proofs in stream.proofs.all %}
-    {% include 'production/_hx_productionstream_actions_proofs_item.html' with i_proof=forloop.counter0|add:1 active_id=total_proofs stream=stream proofs=proofs %}
-  {% empty %}
-    <div>No Proofs found.</div>
-  {% endfor %}
-</div>
+{% load bootstrap %}
 
-<div class="row mt-3">
-  <div class="col-12">
-    <form enctype="multipart/form-data"
-          hx-post="{% url 'production:upload_proofs' stream_id=stream.id %}"
-          hx-target="#productionstream-{{ stream.id }}-upload-proofs-body">
-      {% csrf_token %}
-      {{ form|bootstrap_purely_inline }}
-      <input type="submit"
-             class="btn btn-primary proof-action-button"
-             name="submit"
-             value="Upload">
-    </form>
+{% block content %}
+
+  <div class="row">
+    <div class="col-12">
+      <h1 class="highlight">Upload Proofs</h1>
+      {% include 'submissions/_submission_card_content.html' with submission=stream.submission %}
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-12">
+      <form method="post" enctype="multipart/form-data">
+        {% csrf_token %}
+        {{ form|bootstrap }}
+        <input type="submit"
+               class="btn btn-outline-secondary"
+               name="submit"
+               value="Upload">
+      </form>
+    </ul>
   </div>
 </div>
+
+{% endblock content %}
diff --git a/scipost_django/production/tests/test_models.py b/scipost_django/production/tests/test_models.py
index e35c6d72e48182c4ed96b86204e5deeeaf51c62d..78a12ca5f3a62aebb84143c3235e223f80e6d7cb 100644
--- a/scipost_django/production/tests/test_models.py
+++ b/scipost_django/production/tests/test_models.py
@@ -153,7 +153,7 @@ class TestProofRepository(TestCase):
         user_profile.last_name = "Usable User"
         user_profile.save()
 
-        self.assertEqual(proofs_repo.name, "scipost_202101_00001v1_User")
+        self.assertEqual(proofs_repo.name, "scipost_202101_00001v1_Usable-User")
 
     def test_repo_name_two_authors(self):
         proofs_repo = ProofsRepository.objects.get(
@@ -166,6 +166,20 @@ class TestProofRepository(TestCase):
 
         self.assertEqual(proofs_repo.name, "scipost_202101_00001v1_Person")
 
+    def test_repo_name_accented_authors(self):
+        proofs_repo = ProofsRepository.objects.get(
+            stream__submission__preprint__identifier_w_vn_nr="scipost_202101_00001v1"
+        )
+
+        user_profile = Contributor.objects.get(user__username="testuser").profile
+        user_profile.first_name = "Some"
+        user_profile.last_name = "Pérsønüsær (陈)"
+        user_profile.save()
+
+        proofs_repo.stream.submission.author_list = "Some Pérsønüsær (陈)"
+
+        self.assertEqual(proofs_repo.name, "scipost_202101_00001v1_Personusaer")
+
     def test_repo_paths_scipostphys(self):
         proofs_repo = ProofsRepository.objects.get(
             stream__submission__preprint__identifier_w_vn_nr="scipost_202101_00001v1"
diff --git a/scipost_django/production/urls.py b/scipost_django/production/urls.py
index 81e04bc2e833da0da4b1dbdd2c9a1ce49bba0bd0..54b32e500a26161a0ccefe456f6d2454c9d2f932 100644
--- a/scipost_django/production/urls.py
+++ b/scipost_django/production/urls.py
@@ -47,6 +47,11 @@ urlpatterns = [
         production_views._hx_productionstream_list,
         name="_hx_productionstream_list",
     ),
+    path(
+        "_hx_productionstream_search_form/<str:filter_set>",
+        production_views._hx_productionstream_search_form,
+        name="_hx_productionstream_search_form",
+    ),
     path(
         "_hx_productionstream_actions_bulk_assign_officers",
         production_views._hx_productionstream_actions_bulk_assign_officers,
@@ -134,6 +139,11 @@ urlpatterns = [
             [
                 path("", production_views.stream, name="stream"),
                 path("status", production_views.update_status, name="update_status"),
+                path(
+                    "_hx_status",
+                    production_views._hx_update_status,
+                    name="_hx_update_status",
+                ),
                 path(
                     "proofs/",
                     include(
@@ -143,6 +153,11 @@ urlpatterns = [
                                 production_views.upload_proofs,
                                 name="upload_proofs",
                             ),
+                            path(
+                                "_hx_upload",
+                                production_views._hx_upload_proofs,
+                                name="_hx_upload_proofs",
+                            ),
                             path(
                                 "<int:version>/",
                                 include(
@@ -209,8 +224,8 @@ urlpatterns = [
                             ),
                             path(
                                 "update",
-                                production_views.update_officer,
-                                name="update_officer",
+                                production_views._hx_update_officer,
+                                name="_hx_update_officer",
                             ),
                         ]
                     ),
@@ -231,8 +246,8 @@ urlpatterns = [
                             ),
                             path(
                                 "update",
-                                production_views.update_invitations_officer,
-                                name="update_invitations_officer",
+                                production_views._hx_update_invitations_officer,
+                                name="_hx_update_invitations_officer",
                             ),
                         ]
                     ),
@@ -253,12 +268,17 @@ urlpatterns = [
                             ),
                             path(
                                 "update",
-                                production_views.update_supervisor,
-                                name="update_supervisor",
+                                production_views._hx_update_supervisor,
+                                name="_hx_update_supervisor",
                             ),
                         ]
                     ),
                 ),
+                path(
+                    "_hx_mark_completed",
+                    production_views._hx_mark_as_completed,
+                    name="_hx_mark_as_completed",
+                ),
                 path(
                     "mark_completed",
                     production_views.mark_as_completed,
diff --git a/scipost_django/production/utils.py b/scipost_django/production/utils.py
index 994670e10bac7974c5513cbcaeb846283b48fa2f..e92aab229641424438efebd1af4419b0e1488c70 100644
--- a/scipost_django/production/utils.py
+++ b/scipost_django/production/utils.py
@@ -67,3 +67,27 @@ class ProductionUtils(BaseMailUtil):
             [cls._context["stream"].supervisor.user.email],
             "SciPost: you have a new supervisory task",
         )
+
+    @classmethod
+    def email_assigned_production_officer_bulk(cls):
+        """
+        Email officer about his/her new assigned stream.
+        """
+        cls._send_mail(
+            cls,
+            "email_assigned_production_officer_bulk",
+            [cls._context["officer"].user.email],
+            "SciPost: you have new production tasks",
+        )
+
+    @classmethod
+    def email_assigned_supervisor_bulk(cls):
+        """
+        Email supervisor about his/her new assigned stream.
+        """
+        cls._send_mail(
+            cls,
+            "email_assigned_supervisor_bulk",
+            [cls._context["supervisor"].user.email],
+            "SciPost: you have new supervisory tasks",
+        )
diff --git a/scipost_django/production/views.py b/scipost_django/production/views.py
index f95e42c53d774d8a4a245602a405609f7c38ef8b..ce5b01f43f44f553adce53f811edc87a6bed519b 100644
--- a/scipost_django/production/views.py
+++ b/scipost_django/production/views.py
@@ -455,6 +455,29 @@ def _hx_productionstream_actions_work_log(request, productionstream_id):
     )
 
 
+is_production_user()
+
+
+@permission_required(
+    "scipost.can_take_decisions_related_to_proofs", raise_exception=True
+)
+def update_status(request, stream_id):
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm("can_perform_supervisory_actions", stream):
+        return redirect(reverse("production:production", args=(stream.id,)))
+
+    p = request.user.production_user
+    form = StreamStatusForm(request.POST or None, instance=stream, production_user=p)
+
+    if form.is_valid():
+        stream = form.save()
+        messages.warning(request, "Production Stream succesfully changed status.")
+    else:
+        messages.warning(request, "The status change was invalid.")
+    return redirect(stream.get_absolute_url())
+
+
 @is_production_user()
 @permission_required_htmx(
     (
@@ -464,7 +487,7 @@ def _hx_productionstream_actions_work_log(request, productionstream_id):
     message="You do not have permission to update the status of this stream.",
     css_class="row",
 )
-def update_status(request, stream_id):
+def _hx_update_status(request, stream_id):
     productionstream = get_object_or_404(
         ProductionStream.objects.ongoing(), pk=stream_id
     )
@@ -572,7 +595,7 @@ def remove_officer(request, stream_id, officer_id):
     css_class="row",
 )
 @transaction.atomic
-def update_officer(request, stream_id):
+def _hx_update_officer(request, stream_id):
     productionstream = get_object_or_404(
         ProductionStream.objects.ongoing(), pk=stream_id
     )
@@ -742,7 +765,7 @@ def remove_invitations_officer(request, stream_id, officer_id):
     css_class="row",
 )
 @transaction.atomic
-def update_invitations_officer(request, stream_id):
+def _hx_update_invitations_officer(request, stream_id):
     productionstream = get_object_or_404(
         ProductionStream.objects.ongoing(), pk=stream_id
     )
@@ -886,7 +909,7 @@ class UpdateEventView(UpdateView):
     css_class="row",
 )
 @transaction.atomic
-def update_supervisor(request, stream_id):
+def _hx_update_supervisor(request, stream_id):
     productionstream = get_object_or_404(
         ProductionStream.objects.ongoing(), pk=stream_id
     )
@@ -973,7 +996,7 @@ class DeleteEventView(DeleteView):
 @is_production_user()
 @permission_required("scipost.can_publish_accepted_submission", raise_exception=True)
 @transaction.atomic
-def mark_as_completed(request, stream_id):
+def _hx_mark_as_completed(request, stream_id):
     productionstream = get_object_or_404(
         ProductionStream.objects.ongoing(), pk=stream_id
     )
@@ -995,12 +1018,32 @@ def mark_as_completed(request, stream_id):
     )
 
     return HttpResponse(
-        r"""<summary class="text-white bg-success summary-unstyled p-3">
+        r"""<summary class="text-white bg-success p-3">
                 Production Stream has been marked as completed.
             </summary>"""
     )
 
 
+@is_production_user()
+@permission_required("scipost.can_publish_accepted_submission", raise_exception=True)
+@transaction.atomic
+def mark_as_completed(request, stream_id):
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    stream.status = constants.PRODUCTION_STREAM_COMPLETED
+    stream.closed = timezone.now()
+    stream.save()
+
+    prodevent = ProductionEvent(
+        stream=stream,
+        event="status",
+        comments=" marked the Production Stream as completed.",
+        noted_by=request.user.production_user,
+    )
+    prodevent.save()
+    messages.success(request, "Stream marked as completed.")
+    return redirect(reverse("production:production"))
+
+
 @is_production_user()
 @permission_required_htmx(
     (
@@ -1010,7 +1053,7 @@ def mark_as_completed(request, stream_id):
     message="You cannot upload proofs for this stream.",
 )
 @transaction.atomic
-def upload_proofs(request, stream_id):
+def _hx_upload_proofs(request, stream_id):
     """
     Called by a member of the Production Team.
     Upload the production version .pdf of a submission.
@@ -1048,6 +1091,51 @@ def upload_proofs(request, stream_id):
         prodevent.save()
 
     context = {"stream": stream, "form": form, "total_proofs": stream.proofs.count()}
+    return render(request, "production/_hx_upload_proofs.html", context)
+
+
+@is_production_user()
+@permission_required("scipost.can_upload_proofs", raise_exception=True)
+@transaction.atomic
+def upload_proofs(request, stream_id):
+    """
+    Called by a member of the Production Team.
+    Upload the production version .pdf of a submission.
+    """
+    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm("can_work_for_stream", stream):
+        return redirect(reverse("production:production"))
+
+    form = ProofsUploadForm(request.POST or None, request.FILES or None)
+    if form.is_valid():
+        proofs = form.save(commit=False)
+        proofs.stream = stream
+        proofs.uploaded_by = request.user.production_user
+        proofs.save()
+        Proofs.objects.filter(stream=stream).exclude(version=proofs.version).exclude(
+            status=constants.PROOFS_ACCEPTED
+        ).update(status=constants.PROOFS_RENEWED)
+        messages.success(request, "Proof uploaded.")
+
+        # Update Stream status
+        if stream.status == constants.PROOFS_TASKED:
+            stream.status = constants.PROOFS_PRODUCED
+            stream.save()
+        elif stream.status == constants.PROOFS_RETURNED:
+            stream.status = constants.PROOFS_CORRECTED
+            stream.save()
+
+        prodevent = ProductionEvent(
+            stream=stream,
+            event="status",
+            comments="New Proofs uploaded, version {v}".format(v=proofs.version),
+            noted_by=request.user.production_user,
+        )
+        prodevent.save()
+        return redirect(stream.get_absolute_url())
+
+    context = {"stream": stream, "form": form}
     return render(request, "production/upload_proofs.html", context)
 
 
@@ -1485,6 +1573,22 @@ def _hx_productionstream_summary_assignees_status(request, productionstream_id):
     )
 
 
+def _hx_productionstream_search_form(request, filter_set: str):
+    productionstream_search_form = ProductionStreamSearchForm(
+        user=request.user,
+        session_key=request.session.session_key,
+    )
+
+    if filter_set == "empty":
+        productionstream_search_form.apply_filter_set({}, none_on_empty=True)
+    # TODO: add more filter sets saved in the session of the user
+
+    context = {
+        "form": productionstream_search_form,
+    }
+    return render(request, "production/_hx_productionstream_search_form.html", context)
+
+
 def _hx_event_list(request, productionstream_id):
     productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
 
@@ -1562,10 +1666,16 @@ def _hx_productionstream_actions_bulk_assign_officers(request):
                         "can_work_for_stream", old_officer.user, productionstream
                     )
 
-                # Temp fix.
-                # TODO: Implement proper email
-                ProductionUtils.load({"request": request, "stream": productionstream})
-                ProductionUtils.email_assigned_production_officer()
+            # Temp fix.
+            # TODO: Implement proper email
+            ProductionUtils.load(
+                {
+                    "request": request,
+                    "officer": officer,
+                    "streams": form.productionstreams,
+                }
+            )
+            ProductionUtils.email_assigned_production_officer_bulk()
 
             messages.success(
                 request,
@@ -1614,10 +1724,16 @@ def _hx_productionstream_actions_bulk_assign_officers(request):
                     productionstream,
                 )
 
-            # Temp fix.
-            # TODO: Implement proper email
-            ProductionUtils.load({"request": request, "stream": productionstream})
-            ProductionUtils.email_assigned_supervisor()
+        # Temp fix.
+        # TODO: Implement proper email
+        ProductionUtils.load(
+            {
+                "request": request,
+                "supervisor": supervisor,
+                "streams": form.productionstreams,
+            }
+        )
+        ProductionUtils.email_assigned_supervisor_bulk()
 
         messages.success(
             request,
diff --git a/scipost_django/scipost/static/scipost/assets/config/preconfig.scss b/scipost_django/scipost/static/scipost/assets/config/preconfig.scss
index 56124499ce0e1c769f7be5d798e68acf7c3e6052..75c460daa87e3cec1694b07b4980e818a7a02481 100644
--- a/scipost_django/scipost/static/scipost/assets/config/preconfig.scss
+++ b/scipost_django/scipost/static/scipost/assets/config/preconfig.scss
@@ -202,15 +202,6 @@ $theme-colors: (
 $theme-colors-rgb: map-loop($theme-colors, to-rgb, "$value");
 
 
-// Browser specific fixes
-// Select summary in details with list-style: none and remove triangle for safari
-.summary-unstyled {
-  list-style: none;
-  &::-webkit-details-marker {
-    display: none;
-  }
-}
-
 // Utilities for common display issues
 // Hide div
 .d-none-empty:empty {
@@ -220,4 +211,36 @@ $theme-colors-rgb: map-loop($theme-colors, to-rgb, "$value");
 .checkbox-lg {
   width: 1.5em !important;
   height: 1.5em !important;
+}
+
+summary {
+  // Remove triangle for webkit browsers causing problems with flexbox
+  &::-webkit-details-marker {
+    display: none;
+  }
+
+  // Remove all list styles
+  &.list-none {
+    list-style: none;
+  }
+  
+  // List triangle for summary element (necessary with display: flex)
+  &.list-triangle {
+    position: relative;
+    padding-left: 2em !important;
+
+    // Styling the equilateral triangle
+    &::before {
+      content: "â–¶";
+      position: absolute;
+      left: 0.75em;
+      top: 50%;
+      transform: translateY(-50%);
+    }
+  }
+}
+
+// Rotate the equilateral triangle when summary is open
+details[open] summary.list-triangle::before {
+  content: "â–¼";
 }
\ No newline at end of file
diff --git a/scipost_django/templates/email/email_assigned_production_officer_bulk.html b/scipost_django/templates/email/email_assigned_production_officer_bulk.html
new file mode 100644
index 0000000000000000000000000000000000000000..c4b31feb1a983102270209edb34f8924d4d8e308
--- /dev/null
+++ b/scipost_django/templates/email/email_assigned_production_officer_bulk.html
@@ -0,0 +1,15 @@
+<p>Dear {{ officer.user.last_name }},</p>
+<p>You are now assigned as Production Officer to the streams:</p>
+
+<ul>
+    {% for stream in streams %}<li>{{ stream }}</li>{% endfor %}
+</ul>
+
+<p>
+    The streams will now be open for you on the <a href="https://{{ domain }}{% url 'production:production_new' %}">Production page</a>.
+</p>
+
+
+<p>
+    <em>This mail is automatically generated and therefore not signed.</em>
+</p>
diff --git a/scipost_django/templates/email/email_assigned_production_officer_bulk.txt b/scipost_django/templates/email/email_assigned_production_officer_bulk.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4fff3519388a6a12a5033bde96b2358e413e0f95
--- /dev/null
+++ b/scipost_django/templates/email/email_assigned_production_officer_bulk.txt
@@ -0,0 +1,10 @@
+Dear {{ officer.user.last_name }},
+
+You are now assigned as Production Officer to the streams:
+
+{% for stream in streams %}* {{ stream }}
+{% endfor %}
+
+The stream will now be open for you on the Production page (https://{{ domain }}{% url 'production:production_new' %}).
+
+This mail is automatically generated and therefore not signed.
diff --git a/scipost_django/templates/email/email_assigned_supervisor_bulk.html b/scipost_django/templates/email/email_assigned_supervisor_bulk.html
new file mode 100644
index 0000000000000000000000000000000000000000..9fa51f280a5276cea9c823c33108dc971096fa99
--- /dev/null
+++ b/scipost_django/templates/email/email_assigned_supervisor_bulk.html
@@ -0,0 +1,15 @@
+<p>Dear {{ supervisor.user.last_name }},</p>
+<p>You are now assigned as Production Supervisor to the streams:</p>
+
+<ul>
+    {% for stream in streams %}<li>{{ stream }}</li>{% endfor %}
+</ul>
+
+<p>
+    The streams will now be open for you on the <a href="https://{{ domain }}{% url 'production:production_new' %}">Production page</a>.
+</p>
+
+
+<p>
+    <em>This mail is automatically generated and therefore not signed.</em>
+</p>
diff --git a/scipost_django/templates/email/email_assigned_supervisor_bulk.txt b/scipost_django/templates/email/email_assigned_supervisor_bulk.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dfa9a2a901b8773d9250475994336c024f895931
--- /dev/null
+++ b/scipost_django/templates/email/email_assigned_supervisor_bulk.txt
@@ -0,0 +1,10 @@
+Dear {{ supervisor.user.last_name }},
+
+You are now assigned as Production Supervisor to the streams:
+
+{% for stream in streams %}* {{ stream }}
+{% endfor %}
+
+The stream will now be open for you on the Production page (https://{{ domain }}{% url 'production:production_new' %}).
+
+This mail is automatically generated and therefore not signed.