diff --git a/scipost_django/finances/templates/finances/_logs.html b/scipost_django/finances/templates/finances/_logs.html
index 9dd7c7b15375d1c3f9cba3c5d35ea95434bc7c4c..e5576bb250c9cf78f28dab580cf4ccaadc491e52 100644
--- a/scipost_django/finances/templates/finances/_logs.html
+++ b/scipost_django/finances/templates/finances/_logs.html
@@ -1,29 +1,38 @@
 {% load scipost_extras %}
 
-<ul class="list-unstyled">
+<div class="container">
   {% for log in logs %}
-    <li id="log_{{ log.slug }}" class="pb-2">
-      <div class="d-flex justify-content-between">
-        <div>
-          <strong>{{ log.user.first_name }} {{ log.user.last_name }}</strong>
-          <br>
-          <span class="text-muted">{{ log.log_type }}</span>
+    <div id="log_{{ log.slug }}" class="row">
+      <div class="col">
+        <strong>{{ log.user.first_name }} {{ log.user.last_name }}</strong>
+        <br>
+        <span class="text-muted">{{ log.log_type }}</span>
+        <br>
+        {{ log.comments|linebreaksbr }}
+      </div>
+      <div class="text-muted text-end col-auto d-flex">
+        <div class="">
+          {{ log.work_date }}
           <br>
-          {{ log.comments|linebreaksbr }}
+          <strong>Duration: {{ log.duration|duration }}</strong>
         </div>
-        <div class="text-muted text-end d-flex justify-content-end">
-          <div>
-            {{ log.work_date }}
-            <br>
-            <strong>Duration: {{ log.duration|duration }}</strong>
-          </div>
-          <div class="ps-2">
-            <a class="text-danger" href="{% url 'finances:log_delete' log.slug %}"><span aria-hidden="true">{% include 'bi/trash-fill.html' %}</a>
-          </div>
+        <div class="ps-2">
+          {% if log.user == request.user %}
+            <a id="log_{{ log.slug }}_delete_btn"
+               class="text-danger work_log_delete_btn"
+               hx-get="{% url 'finances:log_delete' log.slug %}"
+               hx-target="#log_{{ log.slug }}"
+               hx-confirm="Delete this log?">
+              <span aria-hidden="true">{% include 'bi/trash-fill.html' %}</span>
+            </a>
+          {% else %}
+            <span class="opacity-0">{% include 'bi/trash-fill.html' %}</span>
+          {% endif %}
         </div>
+ 
       </div>
-    </li>
+    </div>
   {% empty %}
-    <li>No logs were found.</li>
+    <div>No logs were found.</div>
   {% endfor %}
-</ul>
+</div>
diff --git a/scipost_django/finances/templates/finances/personal_timesheet.html b/scipost_django/finances/templates/finances/personal_timesheet.html
new file mode 100644
index 0000000000000000000000000000000000000000..a008b610089ae980708fa53d42329029aaca882a
--- /dev/null
+++ b/scipost_django/finances/templates/finances/personal_timesheet.html
@@ -0,0 +1,47 @@
+
+{% extends 'production/base.html' %}
+
+{% block pagetitle %}
+    : Production team
+{% endblock pagetitle %}
+
+{% load scipost_extras %}
+{% load bootstrap %}
+
+{% block content %}
+
+    <div class="row">
+        <div class="col-12">
+            <h2 class="highlight">My Timesheet</h2>
+        </div>
+    </div>
+
+    <table class="table mb-5">
+        <thead class="table-light">
+            <tr>
+                <th>Date</th>
+                <th>Comment</th>
+                <th>Stream</th>
+                <th>Log type</th>
+                <th>Duration</th>
+            </tr>
+        </thead>
+
+        <tbody role="tablist">
+            {% for log in request.user.work_logs.all %}
+                <tr>
+                    <td>{{ log.work_date }}</td>
+                    <td>{{ log.comments }}</td>
+                    <td>{{ log.content }}</td>
+                    <td>{{ log.log_type }}</td>
+                    <td>{{ log.duration|duration }}</td>
+                </tr>
+            {% empty %}
+                <tr>
+                    <td colspan="4">No logs found.</td>
+                </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+
+{% endblock content %}
diff --git a/scipost_django/finances/urls.py b/scipost_django/finances/urls.py
index 0e76d34e052a3a05441546c3ed583c836cf000d6..8bb329b5e40d5e24564cfed3ecb8c688ce1a9e5c 100644
--- a/scipost_django/finances/urls.py
+++ b/scipost_django/finances/urls.py
@@ -122,7 +122,8 @@ urlpatterns = [
     # Timesheets
     path("timesheets", views.timesheets, name="timesheets"),
     path("timesheets/detailed", views.timesheets_detailed, name="timesheets_detailed"),
-    path("logs/<slug:slug>/delete", views.LogDeleteView.as_view(), name="log_delete"),
+    path("timesheets/mine", views.personal_timesheet, name="personal_timesheet"),
+    path("logs/<slug:slug>/delete", views._hx_worklog_delete, name="log_delete"),
     # PeriodicReports
     path("periodicreport/<int:pk>/file", views.periodicreport_file, name="periodicreport_file"),
 ]
diff --git a/scipost_django/finances/views.py b/scipost_django/finances/views.py
index d91c6e0ec37f0e676af4bb416584dc8dbb600b78..85be62e83d867d2d9e7e737a8d5312430d21dfa3 100644
--- a/scipost_django/finances/views.py
+++ b/scipost_django/finances/views.py
@@ -39,6 +39,7 @@ from comments.utils import validate_file_extention
 from journals.models import Journal, Publication
 from organizations.models import Organization
 from scipost.mixins import PermissionsMixin
+from scipost.views import HTMXPermissionsDenied, HTMXResponse
 
 
 def publishing_years():
@@ -551,6 +552,27 @@ class LogDeleteView(LoginRequiredMixin, DeleteView):
         return self.object.content.get_absolute_url()
 
 
+@permission_required("scipost.can_view_production", raise_exception=True)
+def _hx_worklog_delete(request, slug):
+    log = get_object_or_404(WorkLog, pk=slug_to_id(slug))
+
+    if request.user != log.user:
+        return HTMXPermissionsDenied(
+            "You do not have permission to delete this work log."
+        )
+
+    log.delete()
+
+    return HTMXResponse("Work log has been deleted.", tag="danger")
+
+
+def personal_timesheet(request):
+    """
+    Overview of the user's timesheets across all production streams.
+    """
+    return render(request, "finances/personal_timesheet.html")
+
+
 ###################
 # PeriodicReports #
 ###################
diff --git a/scipost_django/mails/templates/mails/_hx_mail_form.html b/scipost_django/mails/templates/mails/_hx_mail_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..6e9ae47eec4d012a53925014a7a4fe744c75a476
--- /dev/null
+++ b/scipost_django/mails/templates/mails/_hx_mail_form.html
@@ -0,0 +1,18 @@
+{% load bootstrap %}
+
+<form hx-post="{{ view_url }}">
+    {% csrf_token %}
+    {% if transfer_data_form %}{{ transfer_data_form }}{% endif %}
+    {{ form|bootstrap }}
+    <div class="form-group row">
+        <div class="offset-md-2 col-md-10">
+            <input class="btn btn-outline-secondary me-2"
+                   type="reset"
+                   value="Reset to default">
+            <button class="btn btn-primary me-2"
+                    type="submit"
+                    name="save"
+                    value="send_from_editor">Send mail</button>
+        </div>
+    </div>
+</form>
diff --git a/scipost_django/proceedings/forms.py b/scipost_django/proceedings/forms.py
index 0ab15c35344ab4656590d595338c97ea30d85021..17c0d8fcdceb6ebfe2aef528b05f666696d49532 100644
--- a/scipost_django/proceedings/forms.py
+++ b/scipost_django/proceedings/forms.py
@@ -23,3 +23,8 @@ 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/constants.py b/scipost_django/production/constants.py
index 556a5ee61c98eb993c3e853723ee7093a8420c99..b8dcdb4afd52257facb2673a645428b43787b88a 100644
--- a/scipost_django/production/constants.py
+++ b/scipost_django/production/constants.py
@@ -4,6 +4,7 @@ __license__ = "AGPL v3"
 
 PRODUCTION_STREAM_INITIATED = "initiated"
 PRODUCTION_STREAM_COMPLETED = "completed"
+PROOFS_SOURCE_REQUESTED = "source_requested"
 PROOFS_TASKED = "tasked"
 PROOFS_PRODUCED = "produced"
 PROOFS_CHECKED = "checked"
@@ -15,6 +16,7 @@ PROOFS_PUBLISHED = "published"
 PROOFS_CITED = "cited"
 PRODUCTION_STREAM_STATUS = (
     (PRODUCTION_STREAM_INITIATED, "New Stream started"),
+    (PROOFS_SOURCE_REQUESTED, "Source files requested"),
     (PROOFS_TASKED, "Supervisor tasked officer with proofs production"),
     (PROOFS_PRODUCED, "Proofs have been produced"),
     (PROOFS_CHECKED, "Proofs have been checked by Supervisor"),
@@ -74,3 +76,16 @@ PRODUCTION_ALL_WORK_LOG_TYPES = (
         "Cited people have been notified/invited to SciPost",
     ),
 )
+
+PROOFS_REPO_UNINITIALIZED = "uninitialized"
+PROOFS_REPO_CREATED = "created"
+PROOFS_REPO_TEMPLATE_ONLY = "template_only"
+PROOFS_REPO_TEMPLATE_FORMATTED = "template_formatted"
+PROOFS_REPO_PRODUCTION_READY = "production_ready"
+PROOFS_REPO_STATUSES = (
+    (PROOFS_REPO_UNINITIALIZED, "The repository does not exist"),
+    (PROOFS_REPO_CREATED, "The repository exists but is empty"),
+    (PROOFS_REPO_TEMPLATE_ONLY, "The repository contains the bare template"),
+    (PROOFS_REPO_TEMPLATE_FORMATTED, "The repository contains the automatically formatted template"),
+    (PROOFS_REPO_PRODUCTION_READY, "The repository is ready for production"),
+)
diff --git a/scipost_django/production/forms.py b/scipost_django/production/forms.py
index d839a224565b3475232291ba8846cc0f9f107340..44af1bec6e71cdcafa5fb6ef831d8e76ab60ed64 100644
--- a/scipost_django/production/forms.py
+++ b/scipost_django/production/forms.py
@@ -6,14 +6,19 @@ import datetime
 
 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.contrib.sessions.backends.db import SessionStore
 
 from crispy_forms.helper import FormHelper
 from crispy_forms.layout import Layout, Div, Field, Submit
 from crispy_bootstrap5.bootstrap5 import FloatingField
+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
@@ -69,17 +74,34 @@ class AssignOfficerForm(forms.ModelForm):
     def save(self, commit=True):
         stream = super().save(False)
         if commit:
-            if stream.status == constants.PRODUCTION_STREAM_INITIATED:
+            if stream.status in [
+                constants.PRODUCTION_STREAM_INITIATED,
+                constants.PROOFS_SOURCE_REQUESTED,
+            ]:
                 stream.status = constants.PROOFS_TASKED
             stream.save()
         return stream
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields["officer"].queryset = ProductionUser.objects.active().filter(
+            user__groups__name="Production Officers"
+        )
+
 
 class AssignInvitationsOfficerForm(forms.ModelForm):
     class Meta:
         model = ProductionStream
         fields = ("invitations_officer",)
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields[
+            "invitations_officer"
+        ].queryset = ProductionUser.objects.active().filter(
+            user__groups__name="Production Officers"
+        )
+
 
 class AssignSupervisorForm(forms.ModelForm):
     class Meta:
@@ -88,7 +110,7 @@ class AssignSupervisorForm(forms.ModelForm):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self.fields["supervisor"].queryset = self.fields["supervisor"].queryset.filter(
+        self.fields["supervisor"].queryset = ProductionUser.objects.active().filter(
             user__groups__name="Production Supervisor"
         )
 
@@ -105,13 +127,15 @@ class StreamStatusForm(forms.ModelForm):
 
     def get_available_statuses(self):
         if self.instance.status in [
-            constants.PRODUCTION_STREAM_INITIATED,
             constants.PRODUCTION_STREAM_COMPLETED,
+            constants.PROOFS_SOURCE_REQUESTED,
             constants.PROOFS_ACCEPTED,
             constants.PROOFS_CITED,
         ]:
             # No status change can be made by User
             return ()
+        elif self.instance.status == constants.PRODUCTION_STREAM_INITIATED:
+            return ((constants.PROOFS_SOURCE_REQUESTED, "Source files requested"),)
         elif self.instance.status == constants.PROOFS_TASKED:
             return ((constants.PROOFS_PRODUCED, "Proofs have been produced"),)
         elif self.instance.status == constants.PROOFS_PRODUCED:
@@ -123,6 +147,7 @@ class StreamStatusForm(forms.ModelForm):
             return (
                 (constants.PROOFS_SENT, "Proofs sent to Authors"),
                 (constants.PROOFS_CORRECTED, "Corrections implemented"),
+                (constants.PROOFS_SOURCE_REQUESTED, "Source files requested"),
             )
         elif self.instance.status == constants.PROOFS_SENT:
             return (
@@ -170,20 +195,28 @@ class UserToOfficerForm(forms.ModelForm):
     user = UserModelChoiceField(
         queryset=get_user_model()
         .objects.filter(production_user__isnull=True)
-        .order_by("last_name")
+        .order_by("last_name"),
+        required=False,
     )
 
     class Meta:
         model = ProductionUser
         fields = ("user",)
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.fields["user"].queryset = (
-            self.fields["user"]
-            .queryset.filter(production_user__isnull=True)
-            .order_by("last_name")
-        )
+    def save(self, commit=True):
+        if user := self.cleaned_data["user"]:
+            existing_production_user = ProductionUser.objects.filter(
+                name=f"{user.first_name} {user.last_name}"
+            ).first()
+            if existing_production_user:
+                existing_production_user.user = user
+                existing_production_user.save()
+
+            else:
+                production_user = ProductionUser.objects.create(
+                    name=f"{user.first_name} {user.last_name}", user=user
+                )
+                production_user.save()
 
 
 class ProofsUploadForm(forms.ModelForm):
@@ -248,12 +281,11 @@ class ProofsDecisionForm(forms.ModelForm):
 
 
 class ProductionStreamSearchForm(forms.Form):
-
-    accepted_in = forms.ModelChoiceField(
+    accepted_in = forms.ModelMultipleChoiceField(
         queryset=Journal.objects.active(),
         required=False,
     )
-    proceedings = forms.ModelChoiceField(
+    proceedings = ProceedingsMultipleChoiceField(
         queryset=Proceedings.objects.order_by("-submissions_close"),
         required=False,
     )
@@ -261,72 +293,211 @@ class ProductionStreamSearchForm(forms.Form):
     title = forms.CharField(max_length=512, required=False)
     identifier = forms.CharField(max_length=128, required=False)
     officer = forms.ModelChoiceField(
-        queryset=ProductionUser.objects.active(),
+        queryset=ProductionUser.objects.active().filter(
+            user__groups__name="Production Officers"
+        ),
         required=False,
+        empty_label="Any",
     )
     supervisor = forms.ModelChoiceField(
-        queryset=ProductionUser.objects.active(),
+        queryset=ProductionUser.objects.active().filter(
+            user__groups__name="Production Supervisor"
+        ),
+        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
+        ],
+        required=False,
+    )
+    orderby = forms.ChoiceField(
+        label="Order by",
+        choices=(
+            ("submission__acceptance_date", "Date accepted"),
+            ("latest_activity_annot", "Latest activity"),
+            (
+                "status,submission__acceptance_date",
+                "Status + Date accepted",
+            ),
+            ("status,latest_activity_annot", "Status + Latest activity"),
+        ),
+        required=False,
+    )
+    ordering = forms.ChoiceField(
+        label="Ordering",
+        choices=(
+            # FIXME: Emperically, the ordering appers to be reversed for dates?
+            ("-", "Ascending"),
+            ("+", "Descending"),
+        ),
         required=False,
     )
 
     def __init__(self, *args, **kwargs):
         self.user = kwargs.pop("user")
+        self.session_key = kwargs.pop("session_key", None)
         super().__init__(*args, **kwargs)
+
+        # Set the initial values of the form fields from the session data
+        if self.session_key:
+            session = SessionStore(session_key=self.session_key)
+
+            for field in self.fields:
+                if field in session:
+                    self.fields[field].initial = session[field]
+
         self.helper = FormHelper()
         self.helper.layout = Layout(
             Div(
-                Div(Field("accepted_in"), css_class="col-lg-6"),
-                Div(Field("proceedings"), css_class="col-lg-6"),
-                css_class="row",
-            ),
-            Div(
-                Div(FloatingField("author"), css_class="col-lg-6"),
-                Div(FloatingField("title"), css_class="col-lg-6"),
-                css_class="row",
-            ),
-            Div(
-                Div(FloatingField("identifier"), css_class="col-lg-6"),
-                css_class="row",
+                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(Field("officer"), css_class="col-lg-6"),
-                Div(Field("supervisor"), css_class="col-lg-6"),
-                css_class="row",
+                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"),
+                        css_class="row mb-0",
+                    ),
+                    Div(
+                        Div(Field("orderby"), css_class="col-6"),
+                        Div(Field("ordering"), css_class="col-6"),
+                        css_class="row mb-0",
+                    ),
+                    css_class="col-sm-9",
+                ),
+                Div(
+                    Field("status", size=len(constants.PRODUCTION_STREAM_STATUS)),
+                    css_class="col-sm-3",
+                ),
+                css_class="row mb-0",
             ),
         )
 
     def search_results(self):
-        streams = ProductionStream.objects.ongoing()
-        if self.cleaned_data.get("accepted_in"):
-            streams = streams.filter(
-                submission__editorialdecision__for_journal\
-                =self.cleaned_data.get("accepted_in"),
+        # Save the form data to the session
+        if self.session_key is not None:
+            session = SessionStore(session_key=self.session_key)
+
+            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 []
             )
-        if self.cleaned_data.get("proceedings"):
-            streams = streams.filter(
-                submission__proceedings=self.cleaned_data.get("proceedings"),
+            session["proceedings"] = (
+                [proceedings.id for proceedings in session.get("proceedings")]
+                if (session.get("proceedings"))
+                else []
             )
-        if self.cleaned_data.get("identifier"):
-            streams = streams.filter(
-                submission__preprint__identifier_w_vn_nr__icontains\
-                =self.cleaned_data.get("identifier"),
+            session["officer"] = (
+                officer.id if (officer := session.get("officer")) else None
             )
-        if self.cleaned_data.get("author"):
+            session["supervisor"] = (
+                supervisor.id if (supervisor := session.get("supervisor")) else None
+            )
+
+            session.save()
+
+        streams = ProductionStream.objects.ongoing()
+
+        streams = streams.annotate(
+            latest_activity_annot=Greatest(Max("events__noted_on"), "opened", "closed")
+        )
+
+        if accepted_in := self.cleaned_data.get("accepted_in"):
             streams = streams.filter(
-                submission__author_list__icontains=self.cleaned_data.get("author"),
+                submission__editorialdecision__for_journal__in=accepted_in,
             )
-        if self.cleaned_data.get("title"):
+        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__title__icontains=self.cleaned_data.get("title"),
+                submission__preprint__identifier_w_vn_nr__icontains=identifier,
             )
-        if self.cleaned_data.get("officer"):
-            streams = streams.filter(officer=self.cleaned_data.get("officer"))
-        if self.cleaned_data.get("supervisor"):
-            streams = streams.filter(supervisor=self.cleaned_data.get("supervisor"))
+        if author := self.cleaned_data.get("author"):
+            streams = streams.filter(submission__author_list__icontains=author)
+        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)
 
         if not self.user.has_perm("scipost.can_view_all_production_streams"):
             # Restrict stream queryset if user is not supervisor
             streams = streams.filter_for_user(self.user.production_user)
-        streams = streams.order_by("opened")
+
+        # Ordering of streams
+        # Only order if both fields are set
+        if (orderby_value := self.cleaned_data.get("orderby")) and (
+            ordering_value := self.cleaned_data.get("ordering")
+        ):
+            # Remove the + from the ordering value, causes a Django error
+            ordering_value = ordering_value.replace("+", "")
+
+            # Ordering string is built by the ordering (+/-), and the field name
+            # from the orderby field split by "," and joined together
+            streams = streams.order_by(
+                *[
+                    ordering_value + order_part
+                    for order_part in orderby_value.split(",")
+                ]
+            )
 
         return streams
+
+
+class BulkAssignOfficersForm(forms.Form):
+    officer = forms.ModelChoiceField(
+        queryset=ProductionUser.objects.active().filter(
+            user__groups__name="Production Officers"
+        ),
+        required=False,
+        empty_label="Unchanged",
+    )
+    supervisor = forms.ModelChoiceField(
+        queryset=ProductionUser.objects.active().filter(
+            user__groups__name="Production Supervisor"
+        ),
+        required=False,
+        empty_label="Unchanged",
+    )
+
+    def __init__(self, *args, **kwargs):
+        self.productionstreams = kwargs.pop("productionstreams", None)
+        super().__init__(*args, **kwargs)
+        self.helper = FormHelper()
+        self.helper.form_id = "productionstreams-bulk-action-form"
+        self.helper.attrs = {
+            "hx-post": reverse(
+                "production:_hx_productionstream_actions_bulk_assign_officers"
+            ),
+            "hx-target": "#productionstream-bulk-assign-officers-container",
+            "hx-swap": "outerHTML",
+            "hx-confirm": "Are you sure you want to assign the selected production streams to the selected officers?",
+        }
+        self.helper.layout = Layout(
+            Div(
+                Div(Field("supervisor"), css_class="col-6 col-md-4 col-lg-3"),
+                Div(Field("officer"), css_class="col-6 col-md-4 col-lg-3"),
+                css_class="row mb-0",
+            ),
+        )
diff --git a/scipost_django/production/models.py b/scipost_django/production/models.py
index e168ea5a5b91bd1c412399caf30e4227097d2f4a..035ffff3f458bc6affa42a58d48df558ec1d58d7 100644
--- a/scipost_django/production/models.py
+++ b/scipost_django/production/models.py
@@ -24,6 +24,8 @@ from .constants import (
     PRODUCTION_STREAM_COMPLETED,
     PROOFS_STATUSES,
     PROOFS_UPLOADED,
+    PROOFS_REPO_STATUSES,
+    PROOFS_REPO_UNINITIALIZED,
 )
 from .managers import (
     ProductionStreamQuerySet,
diff --git a/scipost_django/production/permissions.py b/scipost_django/production/permissions.py
index 841dd945192969a81446bd66df6b8c987ec5dab2..909d5b9a5c4dbe4b40f91c9e5cbbd50c219d94a8 100644
--- a/scipost_django/production/permissions.py
+++ b/scipost_django/production/permissions.py
@@ -3,6 +3,9 @@ __license__ = "AGPL v3"
 
 
 from django.contrib.auth.decorators import user_passes_test
+from scipost.views import HTMXPermissionsDenied
+from functools import wraps
+from django.contrib import messages
 
 
 def is_production_user():
@@ -15,3 +18,27 @@ def is_production_user():
         return False
 
     return user_passes_test(test)
+
+
+def permission_required_htmx(
+    perm,
+    message="You do not have the required permissions.",
+    **message_kwargs,
+):
+    def decorator(view_func):
+        @wraps(view_func)
+        def _wrapped_view(request, *args, **kwargs):
+            if isinstance(perm, str):
+                perms = (perm,)
+            else:
+                perms = perm
+
+            if request.user.has_perms(perms):
+                return view_func(request, *args, **kwargs)
+            else:
+                messages.error(request, message)
+                return HTMXPermissionsDenied(message, **message_kwargs)
+
+        return _wrapped_view
+
+    return decorator
diff --git a/scipost_django/production/templates/production/_hx_productionstream_actions_bulk_assign_officer.html b/scipost_django/production/templates/production/_hx_productionstream_actions_bulk_assign_officer.html
new file mode 100644
index 0000000000000000000000000000000000000000..8a9836231610c9ab08577dae756fa8c13cca8e1b
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_actions_bulk_assign_officer.html
@@ -0,0 +1,15 @@
+{% load crispy_forms_tags %}
+
+<div id="productionstream-bulk-assign-officers-container">
+    <h3>Bulk Assign Officers</h3>
+    <div>{% crispy form %}</div>
+    <div class="row mb-0">
+        <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 id="productionstreams-bulk-action-form-button"
+                        class="btn btn-primary"
+                        form="productionstreams-bulk-action-form">Assign</button>
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/scipost_django/production/templates/production/_hx_productionstream_actions_change_properties.html b/scipost_django/production/templates/production/_hx_productionstream_actions_change_properties.html
new file mode 100644
index 0000000000000000000000000000000000000000..d55ce237bac3026162ba95341331615a9123ca29
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_actions_change_properties.html
@@ -0,0 +1,16 @@
+{% load bootstrap %}
+{% load scipost_extras %}
+{% load guardian_tags %}
+
+{% get_obj_perms request.user for productionstream as "sub_perms" %}
+
+{% if "can_work_for_stream" in sub_perms and perms.scipost.can_take_decisions_related_to_proofs %}
+    {% include "production/_hx_productionstream_change_status.html" with form=status_form stream=productionstream %}
+{% endif %}
+{% if perms.scipost.can_assign_production_supervisor %}
+    {% include "production/_hx_productionstream_change_supervisor.html" with form=supervisor_form stream=productionstream %}
+{% endif %}
+{% if "can_work_for_stream" in sub_perms and perms.scipost.can_assign_production_officer %}
+    {% include "production/_hx_productionstream_change_officer.html" with form=officer_form stream=productionstream %}
+    {% include "production/_hx_productionstream_change_invitations_officer.html" with form=invitations_officer_form stream=productionstream %}
+{% endif %}
diff --git a/scipost_django/production/templates/production/_hx_productionstream_actions_proofs_item.html b/scipost_django/production/templates/production/_hx_productionstream_actions_proofs_item.html
new file mode 100644
index 0000000000000000000000000000000000000000..40dffeb254025366aaaec6608a6adc817bf3340a
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_actions_proofs_item.html
@@ -0,0 +1,86 @@
+<div id="productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-item"
+     class="accordion-item">
+    <h4 class="accordion-header">
+        <button class="accordion-button {% if proofs.version != active_id %}collapsed{% endif %}"
+                type="button"
+                data-bs-toggle="collapse"
+                data-bs-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-body-container"
+                aria-expanded="false"
+                aria-controls="productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-body-container">
+            <div class="row w-100 m-0 pe-2 align-items-center">
+                <div class="col-6 col-sm col-lg-6 col-xl fs-6">Version {{ proofs.version }}</div>
+                <div class="col-6 col-sm-auto col-md-12 col-lg-6 col-xl-auto">{{ proofs.created|date:"DATE_FORMAT" }}</div>
+                <div class="col-12 col-sm-auto badge bg-secondary">{{ proofs.get_status_display|title }}</div>
+            </div>
+        </button>
+    </h4>
+    <div id="productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-body-container"
+         class="accordion-collapse collapse {% if proofs.version == active_id %}show{% endif %}"
+         data-bs-parent="#productionstream-{{ stream.id }}-proofs-list-accordion">
+        <div class="accordion-body">
+            <div class="row">
+                <div class="col">Uploaded by:</div>
+                <div class="col-auto">{{ proofs.uploaded_by.user.first_name }} {{ proofs.uploaded_by.user.last_name }}</div>
+            </div>
+            <div class="row">
+                <div class="col">Accessible for authors:</div>
+                <div class="col-auto">
+                    {{ proofs.accessible_for_authors|yesno:'<strong>Yes</strong>,No'|safe }}
+                </div>
+            </div>
+ 
+            {% comment %} Buttons {% endcomment %}
+            <div id="productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-action-row"
+                 class="row g-2">
+                <div class="col-12 col-sm-6 col-md-12 col-lg-6 h-100 d-none-empty">
+                    <a download
+                       class="row m-0 d-none-empty"
+                       href="{% url 'production:proofs_pdf' proofs.slug %}">
+                        <button class="btn btn-sm btn-secondary">Download Proofs</button>
+                    </a>
+                </div>
+
+                {% if perms.scipost.can_run_proofs_by_authors %}
+                    {% if proofs.status == 'uploaded' %}
+                        <div class="col-6 col-sm-3 col-md-6 col-lg-3 h-100 d-none-empty">
+                            <div class="row m-0 d-none-empty">
+                                <button hx-get="{% url 'production:_hx_proofs_decision' proofs.stream.id proofs.version 'accept' %}"
+                                        hx-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-item"
+                                        hx-swap="outerHTML"
+                                        class="btn btn-sm btn-primary proof-action-button">Accept</button>
+                            </div>
+                        </div>
+                        <div class="col-6 col-sm-3 col-md-6 col-lg-3 h-100 d-none-empty">
+                            <div class="row m-0 d-none-empty">
+                                <button hx-get="{% url 'production:_hx_proofs_decision' proofs.stream.id proofs.version 'decline' %}"
+                                        hx-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-item"
+                                        hx-swap="outerHTML"
+                                        class="btn btn-sm btn-danger proof-action-button">Decline</button>
+                            </div>
+                        </div>
+                    {% elif proofs.status == 'accepted_sup' %}
+                        <div class="col-12 col-sm-6 col-md-12 col-lg-6 h-100 d-none-empty">
+                            <div class="row m-0 d-none-empty">
+                                <button hx-get="{% url 'production:_hx_send_proofs' proofs.stream.id proofs.version %}"
+                                        hx-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-action-row"
+                                        hx-swap="afterend"
+                                        class="btn btn-sm btn-warning">Send proofs to authors</button>
+                            </div>
+                        </div>
+                    {% else %}
+                        <div class="col-12 col-sm-6 col-md-12 col-lg-6 h-100 d-none-empty">
+                            <div class="row m-0 d-none-empty">
+                                <button hx-get="{% url 'production:_hx_toggle_accessibility' proofs.stream.id proofs.version %}"
+                                        hx-target="#productionstream-{{ stream.id }}-proofs-list-accordion-proofs-{{ proofs.version }}-item"
+                                        hx-swap="outerHTML"
+                                        class="btn btn-sm btn-primary">
+                                    {{ proofs.accessible_for_authors|yesno:'Hide,Make accessible' }} for authors
+                                </button>
+                            </div>
+                        </div>
+                    {% endif %}
+                {% endif %}
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/scipost_django/production/templates/production/_hx_productionstream_actions_work_log.html b/scipost_django/production/templates/production/_hx_productionstream_actions_work_log.html
new file mode 100644
index 0000000000000000000000000000000000000000..a907e867d9cb54eb5055fd54c1a6417a172a8213
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_actions_work_log.html
@@ -0,0 +1,31 @@
+{% load guardian_tags %}
+{% load scipost_extras %}
+{% load bootstrap %}
+
+<div class="row">
+    <h3 class="col">Work Logs</h3>
+    {% include 'finances/_logs.html' with logs=productionstream.work_logs.all|dictsort:"created" %}
+ 
+    {% if productionstream.total_duration and productionstream.work_logs.all.count > 1 %}
+        <div class="col-auto ms-auto me-5 border-primary border-top pt-2">
+            Total: <strong>{{ productionstream.total_duration|duration }}</strong>
+        </div>
+
+    {% endif %}
+</div>
+
+{% if work_log_form %}
+    <div class="row mb-0">
+
+        <h4>Add hours to the Stream</h4>
+        <form id="productionstream-{{ productionstream.id }}-work_log_form"
+              hx-post="{% url 'production:_hx_productionstream_actions_work_log' productionstream_id=productionstream.id %}"
+              hx-target="#productionstream-{{ productionstream.id }}-work-log-body"
+              class="mb-2">
+            {% csrf_token %}
+            {{ work_log_form|bootstrap }}
+            <input type="submit" class="btn btn-primary" name="submit" value="Log">
+        </form>
+
+    </div>
+{% endif %}
diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_action_buttons.html b/scipost_django/production/templates/production/_hx_productionstream_change_action_buttons.html
new file mode 100644
index 0000000000000000000000000000000000000000..8e965e85ab2b843094a2fac3d689a21f521e7371
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_action_buttons.html
@@ -0,0 +1,18 @@
+{% comment %} Only show something if the option is about to change {% endcomment %}
+{% if not current_option == new_option %}
+    <div class="row m-0 d-none-empty">
+        {% comment %} If selected option is None, it is being removed {% endcomment %}
+        {% if new_option == "None" %}
+            <button type="submit" class="btn btn-danger">Remove</button>
+            {% comment %} Otherwise differentiate between changing or adding based on current option {% endcomment %}
+        {% else %}
+            <button type="submit" class="btn btn-primary">
+                {% if current_option == "None" %}
+                    Add
+                {% else %}
+                    Change
+                {% endif %}
+            </button>
+        {% endif %}
+    </div>
+{% endif %}
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
new file mode 100644
index 0000000000000000000000000000000000000000..ac9e3efc8ba0ad5ddb719dbb080aa0fda5cb936f
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_invitations_officer.html
@@ -0,0 +1,18 @@
+{% load bootstrap %}
+
+{% 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 %}"
+              hx-target="#productionstream-{{ stream.id }}-update-invitations_officer"
+              hx-swap="outerHTML"
+              class="row mb-0">
+            {% csrf_token %}
+            <div class="col">{{ form|bootstrap_purely_inline }}</div>
+            <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty"
+                 hx-post="{% url 'production:_hx_productionstream_change_action_buttons' stream.id 'invitations_officer' %}"
+                 hx-swap="innerHTML"
+                 hx-trigger="change from:select#productionstream_{{ stream.id }}_id_invitations_officer"
+                 hx-target="this"></div>
+        </form>
+    </div>
+{% endif %}
diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_officer.html b/scipost_django/production/templates/production/_hx_productionstream_change_officer.html
new file mode 100644
index 0000000000000000000000000000000000000000..2efbd7f2ebb9f75e1f2e64f85e11ed8f0248086d
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_officer.html
@@ -0,0 +1,18 @@
+{% load bootstrap %}
+
+{% if form.fields.officer.choices|length > 0 %}
+    <div id="productionstream-{{ stream.id }}-update-officer">
+        <form hx-post="{% url 'production:update_officer' stream.id %}"
+              hx-target="#productionstream-{{ stream.id }}-update-officer"
+              hx-swap="outerHTML"
+              class="row">
+            {% csrf_token %}
+            <div class="col">{{ form|bootstrap_purely_inline }}</div>
+            <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty"
+                 hx-post="{% url 'production:_hx_productionstream_change_action_buttons' stream.id 'officer' %}"
+                 hx-swap="innerHTML"
+                 hx-trigger="change from:select#productionstream_{{ stream.id }}_id_officer"
+                 hx-target="this"></div>
+        </form>
+    </div>
+{% endif %}
diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_status.html b/scipost_django/production/templates/production/_hx_productionstream_change_status.html
new file mode 100644
index 0000000000000000000000000000000000000000..874111480ad50645cf1518a02f55608d585e0069
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_status.html
@@ -0,0 +1,20 @@
+{% load bootstrap %}
+
+{% if form.fields.status.choices|length > 0 %}
+    <form id="productionstream-{{ stream.id }}-update-status"
+          hx-post="{% url 'production:update_status' stream.id %}"
+          hx-target="this"
+          hx-swap="outerHTML"
+          hx-trigger="submit"
+          hx-sync="this:replace">
+        {% csrf_token %}
+        <div class="row">
+            <div class="col">{{ form|bootstrap_purely_inline }}</div>
+            <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty"
+                 hx-post="{% url 'production:_hx_productionstream_change_action_buttons' stream.id 'status' %}"
+                 hx-swap="innerHTML"
+                 hx-trigger="load, change from:select#productionstream_{{ stream.id }}_id_status"
+                 hx-target="this"></div>
+        </div>
+    </form>
+{% endif %}
diff --git a/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html b/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html
new file mode 100644
index 0000000000000000000000000000000000000000..4ce3afd863abb8ac1a9ece6f79340083938592c1
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_change_supervisor.html
@@ -0,0 +1,18 @@
+{% load bootstrap %}
+
+{% if form.fields.supervisor.choices|length > 0 %}
+    <div id="productionstream-{{ stream.id }}-update-supervisor">
+        <form hx-post="{% url 'production:update_supervisor' stream.id %}"
+              hx-target="#productionstream-{{ stream.id }}-update-supervisor"
+              hx-swap="outerHTML"
+              class="row">
+            {% csrf_token %}
+            <div class="col">{{ form|bootstrap_purely_inline }}</div>
+            <div class="col-12 col-sm-auto col-md-12 col-lg-auto h-100 d-none-empty"
+                 hx-post="{% url 'production:_hx_productionstream_change_action_buttons' stream.id 'supervisor' %}"
+                 hx-swap="innerHTML"
+                 hx-trigger="load, change from:select#productionstream_{{ stream.id }}_id_supervisor"
+                 hx-target="this"></div>
+        </form>
+    </div>
+{% endif %}
diff --git a/scipost_django/production/templates/production/_hx_productionstream_details.html b/scipost_django/production/templates/production/_hx_productionstream_details.html
index a78ef59ea7d6c7cac8fd8272ecb3c29a5b285600..2e51ab633cda7337e6d7b40ba0bcd0f0b1c63399 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_details.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_details.html
@@ -1,26 +1,14 @@
 <details id="productionstream-{{ productionstream.id }}-details"
-	 class="border border-2 mx-3 p-2 bg-primary bg-opacity-10"
->
-  <summary style="list-style: none;"
-	   class="p-2"
-  >
+         class="border border-2 mx-3 p-2 bg-primary bg-opacity-10">
+  <summary class="summary-unstyled p-2">
     {% include "production/_productionstream_details_summary_contents.html" with productionstream=productionstream %}
   </summary>
 
-  <button id="indicator-productionstream-{{ productionstream.id }}-details-contents"
-	  class="htmx-indicator btn btn-sm btn-warning p-2"
-	  type="button"
-  >
-    <small><strong>Loading...</strong></small>
-    <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div>
-  </button>
 
   <div id="productionstream-{{ productionstream.id }}-details-contents"
        class="m-2 p-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>
+       hx-indicator="#indicator-productionstream-{{ productionstream.id }}-details-contents"></div>
 
 </details>
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 29b10ba51dd279f89fd129e020e02f7f99c06e58..2fdf85f4ffcb53b7ad0a3267ed5329cd068435e0 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_details_contents.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_details_contents.html
@@ -1,18 +1,258 @@
+{% load guardian_tags %}
+{% load bootstrap %}
+{% load scipost_extras %}
 
-<p>
-  <strong class="text-warning">While the new production page is being built</strong>: go the the <a href="{{ productionstream.get_absolute_url }}" target="_blank">(old) stream detail page</a> to manage this stream.
-</p>
+{% get_obj_perms request.user for productionstream as "sub_perms" %}
 
+<div class="row">
+  {% 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"
+           id="productionstream-{{ productionstream.id }}-actions-accordion">
+        <h3>Actions</h3>
 
-<h3>Events</h3>
-{% include 'production/_productionstream_events.html' with productionstream=productionstream events=productionstream.events.all_without_duration %}
+        {% if perms.scipost.can_take_decisions_related_to_proofs %}
+          <div class="accordion-item">
+            <h2 class="accordion-header"
+                id="productionstream-{{ productionstream.id }}-change-properties-header">
+              <button class="accordion-button fs-6 {% if accordion_default_open == '' or accordion_default_open != 'change-properties' %}collapsed{% endif %}"
+                      type="button"
+                      data-bs-toggle="collapse"
+                      data-bs-target="#productionstream-{{ productionstream.id }}-change-properties"
+                      aria-expanded="true"
+                      aria-controls="productionstream-{{ productionstream.id }}-change-properties">
+                Change Properties
+              </button>
+            </h2>
+            <div id="productionstream-{{ productionstream.id }}-change-properties"
+                 class="accordion-collapse collapse {% if accordion_default_open == 'change-properties' %}show{% endif %}"
+                 aria-labelledby="productionstream-{{ productionstream.id }}-change-properties-header"
+                 data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion">
+              <div id="productionstream-{{ productionstream.id }}-change-properties-body"
+                   class="accordion-body"
+                   hx-get="{% url 'production:_hx_productionstream_actions_change_properties' productionstream_id=productionstream.id %}"
+                   hx-trigger="intersect once, submit from:#productionstream-{{ productionstream.id }}-details-contents target:form delay:1000">
+ 
+                {% comment %} Placeholder before HTMX content loads {% endcomment %}
+                <div class="placeholder-glow">
+                  {% for i_repeat in '1234' %}
+                    <div class="row ">
+                      <div class="col-2">
+                        <div class="w-100 py-1 placeholder"></div>
+                      </div>
+                      <div class="col-8">
+                        <div class="w-100 pb-4 pt-2 placeholder"></div>
+                      </div>
+                      <div class="col-2">
+                        <div class="w-100 pb-4 pt-2 placeholder"></div>
+                      </div>
+                    </div>
+                  {% endfor %}
+                </div>
+                {% comment %} End placeholder {% endcomment %}
 
+              </div>
+            </div>
+          </div>
+        {% endif %}
 
-<button
-    hx-get="{% url 'production:_hx_event_form' productionstream_id=productionstream.id %}"
-    hx-target="#productionstream-{{ productionstream.id }}-event-form"
-    hx-trigger="click"
->
-  Add a comment to this stream
-</button>
-<div id="productionstream-{{ productionstream.id }}-event-form"></div>
+        {% if "can_work_for_stream" in sub_perms %}
+          <div class="accordion-item">
+            <h2 class="accordion-header"
+                id="productionstream-{{ productionstream.id }}-upload-proofs-header">
+              <button class="accordion-button fs-6 {% if accordion_default_open == '' or accordion_default_open != 'upload-proofs' %}collapsed{% endif %}"
+                      type="button"
+                      data-bs-toggle="collapse"
+                      data-bs-target="#productionstream-{{ productionstream.id }}-upload-proofs"
+                      aria-expanded="false"
+                      aria-controls="productionstream-{{ productionstream.id }}-upload-proofs">Upload Proofs</button>
+            </h2>
+            <div id="productionstream-{{ productionstream.id }}-upload-proofs"
+                 class="accordion-collapse collapse {% if accordion_default_open == 'upload-proofs' %}show{% endif %}"
+                 aria-labelledby="productionstream-{{ productionstream.id }}-upload-proofs-header"
+                 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-trigger="intersect once">
+ 
+                {% comment %} Placeholder before HTMX content loads {% endcomment %}
+                <div class="placeholder-glow">
+                  <h3>
+                    <span class="placeholder">Proofs</span>
+                  </h3>
+                  <div class="w-100 mb-2 py-4 placeholder"></div>
+                  <div class="w-100 mb-2 py-4 placeholder"></div>
+                  <div class="row">
+                    <div class="col-3">
+                      <div class="w-100 py-3 placeholder"></div>
+                    </div>
+                    <div class="col-9">
+                      <div class="w-100 py-3 placeholder"></div>
+                    </div>
+                  </div>
+                  <div class="w-25 px-1 py-3 placeholder"></div>
+                </div>
+                {% comment %} End placeholder {% endcomment %}
+
+              </div>
+            </div>
+          </div>
+
+          <div class="accordion-item">
+            <h2 class="accordion-header"
+                id="productionstream-{{ productionstream.id }}-work-log-header">
+              <button class="accordion-button fs-6 {% if accordion_default_open == '' or accordion_default_open != 'work-log' %}collapsed{% endif %}"
+                      type="button"
+                      data-bs-toggle="collapse"
+                      data-bs-target="#productionstream-{{ productionstream.id }}-work-log"
+                      aria-expanded="false"
+                      aria-controls="productionstream-{{ productionstream.id }}-work-log">Work Log</button>
+            </h2>
+            <div id="productionstream-{{ productionstream.id }}-work-log"
+                 class="accordion-collapse collapse {% if accordion_default_open == 'work-log' %}show{% endif %}"
+                 aria-labelledby="productionstream-{{ productionstream.id }}-work-log-header"
+                 data-bs-parent="#productionstream-{{ productionstream.id }}-actions-accordion">
+              <div id="productionstream-{{ productionstream.id }}-work-log-body"
+                   class="accordion-body"
+                   hx-get="{% url 'production:_hx_productionstream_actions_work_log' productionstream_id=productionstream.id %}"
+                   hx-trigger="intersect once, htmx:trigger from:this target:a.work_log_delete_btn delay:1000">
+ 
+                {% comment %} Placeholder before HTMX content loads {% endcomment %}
+                <div class="placeholder-glow">
+                  <div class="row">
+                    <div class="col-3">
+                      <div class="w-100 placeholder"></div>
+                    </div>
+                  </div>
+
+                  {% for i_repeat in '12' %}
+                    <div class="row">
+                      <div class="col-9">
+                        <div class="g-2">
+                          <div class="col-6 placeholder"></div>
+                          <div class="col-8 placeholder"></div>
+                        </div>
+                      </div>
+                      <div class="col-3">
+                        <div class="g-2">
+                          <div class="col-12 placeholder"></div>
+                          <div class="offset-4 col-8 placeholder"></div>
+                        </div>
+                      </div>
+                    </div>
+                  {% endfor %}
+
+                  <div class="w-75 py-1 mb-3 placeholder"></div>
+
+                  {% for i_repeat in '123' %}
+                    <div class="row">
+                      <div class="col-2">
+                        <div class="w-100 py-1 placeholder"></div>
+                      </div>
+                      <div class="col-10">
+                        <div class="w-100 pb-4 placeholder"></div>
+                      </div>
+                    </div>
+                  {% endfor %}
+                </div>
+                {% comment %} End placeholder {% endcomment %}
+
+              </div>
+            </div>
+          </div>
+        {% endif %}
+ 
+      </div>
+
+
+      {% 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">
+            {% 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-target="#productionstream-{{ productionstream.id }}-details">
+                    Mark this stream as completed
+                  </button>
+                </div>
+              </div>
+            {% endif %}
+ 
+            {% if perms.scipost.can_draft_publication and stream.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"
+                     href="{% url 'journals:create_publication' productionstream.submission.preprint.identifier_w_vn_nr %}">
+                    Draft publication
+                  </a>
+                </div>
+              </div>
+            {% endif %}
+          </div>
+ 
+        </div>
+      {% endif %}
+    {% endif %}
+  </div>
+
+  <div id="productionstream-{{ productionstream.id }}-event-container"
+       class="col-12 col-md d-flex flex-column px-3">
+    {% 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"
+         class="overflow-scroll mb-4"
+         style="max-height: max(50vh, 40em)"
+         hx-get="{% url 'production:_hx_event_list' productionstream.id %}"
+         hx-trigger="intersect once, submit from:#productionstream-{{ productionstream.id }}-details target:form delay:500,  click from:#productionstream-{{ productionstream.id }}-details target:.proof-action-button delay:500">
+
+      {% comment %} Placeholder before HTMX content loads {% endcomment %}
+      <table class="table table-bordered table-striped overflow-scroll mb-0"
+             aria-hidden="true">
+        <tr>
+          <th>Date</th>
+          <th>Event</th>
+          <th>Noted by</th>
+          <th>Actions</th>
+        </tr>
+        {% for p in '12345' %}
+          <tr class="placeholder-glow">
+            <td>
+              <div class="placeholder w-100 bg-secondary"></div>
+            </td>
+            <td>
+              <div class="placeholder w-75 bg-secondary"></div>
+            </td>
+            <td>
+              <div class="placeholder w-75 bg-secondary"></div>
+            </td>
+            <td>
+              <div class="placeholder w-100 bg-secondary"></div>
+            </td>
+          </tr>
+          <tr>
+            <td colspan="4" class="placeholder-wave">
+              <div class="placeholder w-25 bg-secondary"></div>
+              <div class="placeholder w-75 bg-secondary"></div>
+            </td>
+          </tr>
+        {% endfor %}
+      </table>
+      {% comment %} End placeholder {% endcomment %}
+
+    </div>
+ 
+    <div id="productionstream-{{ productionstream.id }}-event-new-comment-form">
+      <button hx-get="{% url 'production:_hx_event_form' productionstream_id=productionstream.id %}"
+              hx-target="#productionstream-{{ productionstream.id }}-event-new-comment-form"
+              hx-trigger="click"
+              hx-swap="outerHTML"
+              class="btn btn-primary">Add a comment to this stream</button>
+    </div>
+  </div>
+</div>
diff --git a/scipost_django/production/templates/production/_hx_productionstream_list.html b/scipost_django/production/templates/production/_hx_productionstream_list.html
index 4496009eca0a76eaa3f95e521ae41549e74d73a5..daa14a2689b6da30d143a92ee0a2bb1785e3bd92 100644
--- a/scipost_django/production/templates/production/_hx_productionstream_list.html
+++ b/scipost_django/production/templates/production/_hx_productionstream_list.html
@@ -1,5 +1,7 @@
 {% for productionstream in page_obj %}
-  <div class="ms-3 mt-3"><strong>{{ forloop.counter0|add:start_index }} of {{ count }}</strong></div>
+  <div class="ms-3 mt-3">
+    <strong>{{ forloop.counter0|add:start_index }} of {{ count }}</strong>
+  </div>
   {% include 'production/_hx_productionstream_details.html' with productionstream=productionstream %}
 {% empty %}
   <strong>No Production Stream could be found</strong>
@@ -9,12 +11,15 @@
        hx-include="#search-productionstreams-form"
        hx-trigger="revealed"
        hx-swap="afterend"
-       hx-indicator="#indicator-search-page-{{ page_obj.number }}"
-  >
-    <div id="indicator-search-page-{{ page_obj.number }}" class="htmx-indicator p-2">
+       hx-indicator="#indicator-search-page-{{ page_obj.number }}">
+    <div id="indicator-search-page-{{ page_obj.number }}"
+         class="htmx-indicator p-2">
       <button class="btn btn-warning" type="button" disabled>
-	<strong>Loading page {{ page_obj.next_page_number }} out of {{ page_obj.paginator.num_pages }}</strong>
-	<div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div>
+        <strong>Loading page {{ page_obj.next_page_number }} out of {{ page_obj.paginator.num_pages }}</strong>
+	
+        <div class="spinner-grow spinner-grow-sm ms-2"
+             role="status"
+             aria-hidden="true"></div>
       </button>
     </div>
   </div>
diff --git a/scipost_django/production/templates/production/_hx_productionstream_summary_assignees_status.html b/scipost_django/production/templates/production/_hx_productionstream_summary_assignees_status.html
new file mode 100644
index 0000000000000000000000000000000000000000..96a9c7fe7a4a1e39d36b08259ad60394add6261a
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_productionstream_summary_assignees_status.html
@@ -0,0 +1,58 @@
+{% load scipost_extras %}
+
+<div class="row mb-0">
+  <div class="order-3 order-sm-1 order-md-3 order-xl-1 col-sm-6 col-md-12 col-xl-6">
+    <div class="row justify-content-between">
+      <small class="col text-muted text-nowrap">Stream opened</small>
+      <div class="col-auto">{{ productionstream.opened|timesince }} ago</div>
+    </div>
+  </div>
+  <div class="order-2 order-sm-4 order-md-2 order-xl-4 col-sm-6 col-md-12 col-xl-6">
+    <div class="row justify-content-between">
+      <small class="col text-muted text-nowrap">Officer</small>
+      <div class="col-auto text-nowrap text-truncate">
+        {% if productionstream.officer %}
+          <span class="text-success ">{% include 'bi/check-circle-fill.html' %}</span>
+          {% firstof productionstream.officer.name productionstream.officer %}
+        {% else %}
+          <span class="text-danger">{% include 'bi/x-circle-fill.html' %}</span>
+        {% endif %}
+      </div>
+    </div>
+  </div>
+  <div class="order-4 order-sm-3 order-md-4 order-xl-3 col-sm-6 col-md-12 col-xl-6">
+    <div class="row justify-content-between">
+      <small class="col text-muted text-nowrap">Latest activity</small>
+      <div class="col-auto">{{ productionstream.latest_activity|timesince }} ago</div>
+    </div>
+  </div>
+  <div class="order-1 order-sm-2 order-md-1 order-xl-2 col-sm-6 col-md-12 col-xl-6">
+    <div class="row justify-content-between">
+      <small class="col text-muted text-nowrap">Supervisor</small>
+      <div class="col-auto text-nowrap text-truncate">
+        {% if productionstream.supervisor %}
+          <span class="text-success">{% include 'bi/check-circle-fill.html' %}</span>
+          {% firstof productionstream.supervisor.name productionstream.supervisor %}
+        {% else %}
+          <span class="text-danger">{% include 'bi/x-circle-fill.html' %}</span>
+        {% endif %}
+      </div>
+    </div>
+  </div>
+  <div class="order-5 order-sm-5 order-md-5 order-xl-5 col-sm-6 col-md-12 col-xl-6">
+    <div class="row mb-0 justify-content-between align-items-center">
+      <small class="col text-muted text-nowrap">Stream Status</small>
+      <div class="col-auto">
+        <div class="p-2 badge bg-{% if productionstream.status == 'initiated' %}danger{% else %}primary{% endif %}">
+          {{ productionstream.status|readable_str|title }}
+        </div>
+      </div>
+    </div>
+  </div>
+  <div id="indicator-productionstream-{{ productionstream.id }}-details-contents"
+       class="order-last col-sm-6 col-md-12 col-xl d-none d-sm-flex d-md-none d-xl-flex htmx-indicator justify-content-end">
+    <small class="text-white bg-warning px-2 py-1 ">
+      <strong>Loading...</strong>
+      <span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
+    </small>
+  </div>
diff --git a/scipost_django/production/templates/production/_hx_team_promote_user.html b/scipost_django/production/templates/production/_hx_team_promote_user.html
new file mode 100644
index 0000000000000000000000000000000000000000..7dd5d179428991a54038dfcaccc19f3e76dc4a58
--- /dev/null
+++ b/scipost_django/production/templates/production/_hx_team_promote_user.html
@@ -0,0 +1,15 @@
+{% load bootstrap %}
+
+<h3>Promote user to Production Officer</h3>
+{% if form %}
+  <form hx-post="{% url 'production:_hx_team_promote_user' %}"
+        hx-target="#production-team-promote-user"
+        hx-trigger="submit, htmx:trigger from:body delay:1000"
+        hx-indicator="#production-team-indicator">
+    {% csrf_token %}
+    {{ form|bootstrap }}
+    <input type="submit"
+           class="btn btn-primary"
+           value="Promote to Production Officer">
+  </form>
+{% endif %}
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 3071e39f4b1dda8936863a69d3c3615f2f03945b..6fb15371a18c733794117ae31a8497750b3aab03 100644
--- a/scipost_django/production/templates/production/_productionstream_details_summary_contents.html
+++ b/scipost_django/production/templates/production/_productionstream_details_summary_contents.html
@@ -1,65 +1,147 @@
 <div class="row mb-0">
-  <div class="col col-md-8">
-    <table>
-      <tbody>
-	<tr>
-	  <td><strong class="text-primary">{{ productionstream.submission.title }}</strong></td>
-	</tr>
-	<tr class="mt-1">
-	  <td><strong><em>by {{ productionstream.submission.author_list }}</em></strong></td>
-	</tr>
-      </tbody>
-    </table>
-    <div class="row mt-2 mb-0">
-      <div class="col">
-	<small class="text-muted">To be published in</small><br>
-	{{ productionstream.submission.editorial_decision.for_journal }}
-      </div>
-      <div class="col">
-	<small class="text-muted">Acceptance date</small><br>
-	{{ productionstream.submission.editorial_decision.taken_on|date:'Y-m-d' }}
-      </div>
-      <div class="col">
-	<small class="text-muted">Stream Status</small>
-	<br>
-        <div class="p-2 label label-{% if stream.status == 'initiated' %}outline-danger{% else %}secondary{% endif %}">{{ productionstream.get_status_display }}</div>
-	{% if productionstream.submission.editorial_decision.status == productionstream.submission.editorial_decision.AWAITING_PUBOFFER_ACCEPTANCE %}<br><strong class="text-danger">Wait! author<br>acceptance of puboffer<br>required!</strong>{% endif %}
-      </div>
+    <div class="col col-md-6">
+        <div class="row align-items-center">
+            {% if perms.scipost.can_assign_production_officer or perms.scipost.can_assign_production_supervisor %}
+                <div class="col-auto">
+                    <input type="checkbox"
+                           class="form-check-input checkbox-lg"
+                           name="productionstream-bulk-action-selected"
+                           value="{{ productionstream.id }}"
+                           id="productionstream-{{ productionstream.id }}-checkbox"
+                           form="productionstreams-bulk-action-form">
+                </div>
+            {% endif %}
+            <div class="col text-truncate">
+                <span class="text-truncate">
+                    <strong>
+                        <span class="text-primary" title="{{ productionstream.submission.title }}">
+                            {{ productionstream.submission.title }}
+                        </span>
+                        <br>
+                        <em title="{{ productionstream.submission.author_list }}">by {{ productionstream.submission.author_list }}</em>
+                    </strong>
+                </span>
+            </div>
+            <div class="col-auto">
+                <div class="row mb-0 align-items-center">
+                    <div class="col-auto d-none d-sm-block d-md-none d-lg-block">
+                        <small class="text-muted">Acceptance date</small>
+                        <br>
+                        {{ productionstream.submission.editorial_decision.taken_on|date:'Y-m-d' }}
+                    </div>
+                    <div class="col-auto">
+                        <a href="{% firstof productionstream.submission.preprint.url productionstream.submission.get_absolute_url %}"
+                           target="_blank">
+                            <span style="pointer-events: none;">
+                                {% if productionstream.submission.preprint.is_arXiv %}
+                                    {% include 'bi/arxiv.html' %}
+                                {% else %}
+                                    {% include 'bi/scipost.html' %}
+                                {% endif %}
+                            </span>
+                        </a>
+                    </div>
+                </div>
+            </div>
+        </div>
+ 
+        <div class="row">
+            <div class="col col-sm-6 col-md text-nowrap">
+                <small class="text-muted">To be published in</small>
+                <br>
+                {% if productionstream.submission.proceedings %}
+                    {% if productionstream.submission.proceedings.event_suffix %}
+                        {{ productionstream.submission.proceedings.event_suffix }}
+                    {% else %}
+                        {{ productionstream.submission.proceedings.event_name }}
+                    {% endif %}
+                {% else %}
+                    {{ productionstream.submission.editorial_decision.for_journal }}
+                {% endif %}
+            </div>
+ 
+            <div class="col col-sm-6 col-md text-nowrap">
+                <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 }}
+                {% endif %}
+            </div>
+
+            <div class="col-auto">
+                <small class="text-muted">Go to page:</small>
+                <br>
+                <div class="d-inline-flex">
+                    <a href="{{ productionstream.submission.get_absolute_url }}">Submission</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>
+                    {% endif %}
+                    {% if productionstream.proofs_repository %}
+                        &nbsp;&nbsp; &bullet; &nbsp;&nbsp;
+                        <a href="{{ productionstream.proofs_repository.git_url }}">Git Repo</a>
+                    {% endif %}
+                </div>
+            </div>
+        </div>
+
     </div>
 
-  </div>
-  <div class="col col-md-4 border-start">
-    <table class="table">
-      <tr>
-	<td><small class="text-muted">Latest activity</small></td>
-	<td>
-	  {{ productionstream.latest_activity|timesince }} ago
-	  <br>
-	  <span class="text-muted">(opened {{ productionstream.opened|timesince }} ago)</span>
-	</td>
-      </tr>
-      <tr>
-	<td><small class="text-muted">Supervisor</small></td>
-	<td>
-	  {% if productionstream.supervisor %}
-	    <span class="text-success">{% include 'bi/check-circle-fill.html' %}</span>
-	    {{ productionstream.supervisor }}
-	  {% else %}
-	    <span class="text-danger">{% include 'bi/x-circle-fill.html' %}</span>
-	  {% endif %}
-	</td>
-      </tr>
-      <tr>
-	<td><small class="text-muted">Production officer</small></td>
-	<td>
-	  {% if productionstream.officer %}
-	    <span class="text-success">{% include 'bi/check-circle-fill.html' %}</span>
-	    {{ productionstream.officer }}
-	  {% else %}
-	    <span class="text-danger">{% include 'bi/x-circle-fill.html' %}</span>
-	  {% endif %}
-	</td>
-      </tr>
-    </table>
-  </div>
-</div>
+    <div id="productionstream-{{ productionstream.id }}-summary-assignees"
+         class="col-md-6"
+         hx-get="{% url 'production:_hx_productionstream_summary_assignees_status' productionstream.id %}"
+         hx-trigger="intersect once, submit from:#productionstream-{{ productionstream.id }}-details target:form delay:500, click from:#productionstream-{{ productionstream.id }}-details target:.proof-action-button delay:500, submit from:#productionstreams-filter-details target:#productionstreams-bulk-action-form delay:500">
+
+        {% comment %} Placeholder while HTMX is loading {% endcomment %}
+        <div class="row mb-0 placeholder-glow">
+            <div class="order-3 order-sm-1 order-md-3 order-xl-1 col-sm-6 col-md-12 col-xl-6">
+                <div class="row justify-content-between">
+                    <small class="col-4 text-muted text-nowrap">Stream opened</small>
+                    <div class="col-8">
+                        <div class="ms-2 w-100 placeholder"></div>
+                    </div>
+                </div>
+            </div>
+            <div class="order-2 order-sm-4 order-md-2 order-xl-4 col-sm-6 col-md-12 col-xl-6">
+                <div class="row justify-content-between">
+                    <small class="col-4 text-muted text-nowrap">Officer</small>
+                    <div class="col-8 text-nowrap text-truncate">
+                        <div class="ms-2 w-100 placeholder"></div>
+                    </div>
+                </div>
+            </div>
+            <div class="order-4 order-sm-3 order-md-4 order-xl-3 col-sm-6 col-md-12 col-xl-6">
+                <div class="row justify-content-between">
+                    <small class="col-4 text-muted text-nowrap">Latest activity</small>
+                    <div class="col-8">
+                        <div class="ms-2 w-100 placeholder"></div>
+                    </div>
+                </div>
+            </div>
+            <div class="order-1 order-sm-2 order-md-1 order-xl-2 col-sm-6 col-md-12 col-xl-6">
+                <div class="row justify-content-between">
+                    <small class="col-4 text-muted text-nowrap">Supervisor</small>
+                    <div class="col-8 text-nowrap text-truncate">
+                        <div class="ms-2 w-100 placeholder"></div>
+                    </div>
+                </div>
+            </div>
+            <div class="order-5 order-sm-5 order-md-5 order-xl-5 col-sm-6 col-md-12 col-xl-6">
+                <div class="row mb-0 justify-content-between align-items-center">
+                    <small class="col-4 text-muted text-nowrap">Stream Status</small>
+                    <div class="offset-5 col-3">
+                        <div class="badge bg-primary w-100">
+                            <div class="placeholder py-2"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+ 
+            {% comment %} End placeholder {% endcomment %}
+
+        </div>
+    </div>
diff --git a/scipost_django/production/templates/production/_productionstream_events.html b/scipost_django/production/templates/production/_productionstream_events.html
index 4976228fc2841894ac70a0fa3c194e98a8b123b3..28b1ed7d2fb9dadef06ba3d8d6142cfe57d03147 100644
--- a/scipost_django/production/templates/production/_productionstream_events.html
+++ b/scipost_django/production/templates/production/_productionstream_events.html
@@ -1,7 +1,7 @@
 {% load scipost_extras %}
 {% load automarkup %}
 
-<table class="table table-bordered table-striped">
+<table class="table table-bordered table-striped overflow-scroll mb-0">
   <thead>
     <tr>
       <th>Date</th>
@@ -13,69 +13,73 @@
   <tbody>
     {% for event in events %}
       <tr>
-	<td>
+ 
+        <td>
           {{ event.noted_on }}
           {% if event.duration %}
             <br>
             <strong>Duration: {{ event.duration|duration }}</strong>
           {% endif %}
-	</td>
-	<td>
-          {{ event.get_event_display|linebreaksbr }}
-	</td>
-	<td>
+        </td>
+
+        <td>{{ event.get_event_display|linebreaksbr }}</td>
+
+        <td>
           <strong>{{ event.noted_by.user.first_name }} {{ event.noted_by.user.last_name }}</strong>
-	</td>
-	<td>
+        </td>
+
+        <td>
           {% if not non_editable %}
             {% if event.noted_by == request.user.production_user and event.editable %}
               <div class="ps-2">
-		<a hx-get="{% url 'production:_hx_event_form' productionstream_id=productionstream.id event_id=event.id %}"
-		   hx-target="#productionstream-{{ productionstream.id }}-event-{{ event.id }}-form"
-		>
-		  <span aria-hidden="true">
-		    {% include 'bi/pencil-square.html' %}
-		  </span>
-		</a>
-		<a class="text-danger"
-		   hx-get="{% url 'production:_hx_event_delete' productionstream_id=productionstream.id event_id=event.id %}"
-		   hx-target="#productionstream-{{ productionstream.id }}-details-contents"
-		   hx-confirm="Delete this Event?"
-		>
-		  <span aria-hidden="true">
-		    {% include 'bi/trash-fill.html' %}
-		  </span>
-		</a>
+                <a hx-get="{% url 'production:_hx_event_form' productionstream_id=productionstream.id event_id=event.id %}"
+                   hx-target="#productionstream-{{ productionstream.id }}-event-{{ event.id }}-form">
+                  <span aria-hidden="true">{% include 'bi/pencil-square.html' %}</span>
+                </a>
+                <a class="text-danger"
+                   hx-get="{% url 'production:_hx_event_delete' productionstream_id=productionstream.id event_id=event.id %}"
+                   hx-target="#productionstream-{{ productionstream.id }}-details-contents"
+                   hx-confirm="Delete this Event?">
+                  <span aria-hidden="true">{% include 'bi/trash-fill.html' %}</span>
+                </a>
               </div>
             {% endif %}
           {% endif %}
-	</td>
+        </td>
+	
       </tr>
       <tr>
-	<td colspan="4" class="ps-4 pb-4">
-	  {% if event.comments %}
+	
+        <td colspan="4" class="ps-4 pb-4">
+          {% if event.comments %}
             <p class="mt-2 mb-0">
               {% if event.noted_to %}
-		<strong>To: {{ event.noted_to.user.first_name }} {{ event.noted_to.user.last_name }}</strong>
-		<br>
+                <strong>To: {{ event.noted_to.user.first_name }} {{ event.noted_to.user.last_name }}</strong>
+                <br>
               {% endif %}
-	      {% automarkup event.comments %}
+              {% automarkup event.comments %}
             </p>
-	  {% endif %}
+          {% endif %}
 
-	  {% if event.attachments.exists %}
+ 
+          {% if event.attachments.exists %}
             <ul>
               {% for attachment in event.attachments.all %}
-		<li><a href="{{ attachment.get_absolute_url }}" target="_blank">Download Attachment {{ forloop.counter }}</a></li>
+                <li>
+                  <a href="{{ attachment.get_absolute_url }}" target="_blank">Download Attachment {{ forloop.counter }}</a>
+                </li>
               {% endfor %}
             </ul>
-	  {% endif %}
-	  <div id="productionstream-{{ productionstream.id }}-event-{{ event.id }}-form"></div>
-	</td>
+
+          {% endif %}
+ 
+          <div id="productionstream-{{ productionstream.id }}-event-{{ event.id }}-form"></div>
+        </td>
+	
       </tr>
     {% empty %}
       <tr>
-	<td>No events found</td>
+        <td>No events found</td>
       </tr>
     {% endfor %}
   </tbody>
diff --git a/scipost_django/production/templates/production/_stream_status_changes.html b/scipost_django/production/templates/production/_stream_status_changes.html
index 07efba92e800094e7e5bfe7ed3821df0ca0b6f98..832317d264936565a4a55187503e9ca2a9cac0c8 100644
--- a/scipost_django/production/templates/production/_stream_status_changes.html
+++ b/scipost_django/production/templates/production/_stream_status_changes.html
@@ -1,14 +1,11 @@
 {% load bootstrap %}
 
 {% if perms.scipost.can_take_decisions_related_to_proofs and form.fields.status.choices|length > 0 %}
-  <h3>Change current stream status:</h3>
-  <form method="post" action="{% url 'production:update_status' stream.id %}" class="form-inline">
+  <form method="post" action="{% url 'production:update_status' stream.id %}" class="row">
     {% csrf_token %}
-    {{ form|bootstrap_inline }}
-    <div class="form-group row">
-      <div class="col-form-label col ms-2">
-        <button type="submit" class="btn btn-primary">Change</button>
-      </div>
+    {{ form|bootstrap_purely_inline }}
+    <div class="col-auto">
+      <button type="submit" class="btn btn-primary">Change</button>
     </div>
   </form>
 {% endif %}
diff --git a/scipost_django/production/templates/production/production_new.html b/scipost_django/production/templates/production/production_new.html
index d8f6ab4dd6726949c1d045586c099d209d50994f..388326ba4d72a5a47cc83c2762a180b4e5d23279 100644
--- a/scipost_django/production/templates/production/production_new.html
+++ b/scipost_django/production/templates/production/production_new.html
@@ -2,46 +2,71 @@
 
 {% load crispy_forms_tags %}
 
-{% block breadcrumb_items %}
-  <span class="breadcrumb-item">Production streams</span>
-{% endblock %}
+{% block breadcrumb_items %}<span class="breadcrumb-item">Production streams</span>{% endblock %}
 
-{% block pagetitle %}: Production page{% endblock pagetitle %}
+{% block pagetitle %}
+  : Production page
+{% endblock pagetitle %}
 
 
 {% block content %}
 
-  <h1>Production Streams</h1>
-
-  <div class="card my-4">
-    <div class="card-header">
-      Search / filter
+  <div class="row">
+    <div class="col-12 col-sm">
+      <h1>Production Streams</h1>
     </div>
-    <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 class="col-12 col-sm-auto">
+      {% if perms.scipost.can_promote_user_to_production_officer %}
+        <a class="btn-link fs-6" href="{% url 'production:production_team' %}">Production Team</a>
+        &nbsp;|&nbsp;
+      {% endif %}
+      <a class="btn-link fs-6" href="{% url 'finances:personal_timesheet' %}">Personal Timesheet</a>
     </div>
   </div>
 
-  <div class="row">
-    <div class="col">
-      <em>The list should update automatically. Feels stuck?</em>&nbsp;<a id="refresh-button" class="m-2 btn btn-primary">{% include "bi/arrow-clockwise.html" %}&nbsp;Refresh</a>
-    </div>
-    <div class="col">
-      <div id="indicator-search-productionstreams" class="htmx-indicator">
-	<button class="btn btn-sm btn-warning" type="button" disabled>
-	  <strong>Loading...</strong>
-	  <div class="spinner-grow spinner-grow-sm ms-2" role="status" aria-hidden="true"></div>
-	</button>
+  <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">
+        <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>
+            <strong>Loading...</strong>
+ 
+            <div class="spinner-grow spinner-grow-sm ms-2"
+                 role="status"
+                 aria-hidden="true"></div>
+ 
+          </button>
+        </div>
+
+        <a id="refresh-button" class="m-2 btn btn-primary">
+          {% include "bi/arrow-clockwise.html" %}
+        &nbsp;Refresh</a>
       </div>
+
+    </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>
+
+      {% comment %} Bulk Action buttons {% endcomment %}
+
+      {% if perms.scipost.can_assign_production_officer or perms.scipost.can_assign_production_supervisor %}
+        <hr>
+        <div hx-get="{% url 'production:_hx_productionstream_actions_bulk_assign_officers' %}"
+             hx-trigger="load"></div>
+      {% endif %}
     </div>
-  </div>
+  </details>
+
   <div id="search-productionstreams-results" class="mt-2"></div>
 
 {% endblock content %}
diff --git a/scipost_django/production/templates/production/production_team.html b/scipost_django/production/templates/production/production_team.html
new file mode 100644
index 0000000000000000000000000000000000000000..532a6a3d019608c611f26507aaf19fe3b82b3d08
--- /dev/null
+++ b/scipost_django/production/templates/production/production_team.html
@@ -0,0 +1,34 @@
+{% extends 'production/base.html' %}
+
+{% block pagetitle %}
+    : Production team
+{% endblock pagetitle %}
+
+{% load scipost_extras %}
+
+{% block content %}
+    {% if perms.scipost.can_promote_user_to_production_officer %}
+        <h2 class="highlight mb-4 d-flex flex-row">
+            Production Team
+            <div class="htmx-indicator ms-auto" id="production-team-indicator">
+                <button class="btn btn-sm btn-warning" type="button" disabled>
+                    <strong>Loading...</strong>
+                    <div class="spinner-grow spinner-grow-sm ms-2"
+                         role="status"
+                         aria-hidden="true"></div>
+                </button>
+            </div>
+        </h2>
+        <div class="row">
+            <div id="production-team-delete-officer"
+                 class="col-12 col-md-6"
+                 hx-get="{% url 'production:production_team_list' %}"
+                 hx-trigger="load, submit from:body target:form delay:1000"
+                 hx-indicator="#production-team-indicator"></div>
+            <div id="production-team-promote-user" class="col-12 col-md-6">
+                {% include 'production/_hx_team_promote_user.html' with form=new_officer_form %}
+            </div>
+        </div>
+
+    {% endif %}
+{% endblock content %}
diff --git a/scipost_django/production/templates/production/production_team_list.html b/scipost_django/production/templates/production/production_team_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..34ce85e58bf5d41ba58c066b5dcc9a6efac08d64
--- /dev/null
+++ b/scipost_django/production/templates/production/production_team_list.html
@@ -0,0 +1,15 @@
+<h3>Current Production Team</h3>
+<ul>
+    {% for officer in officers %}
+        <li id="production-team-officer-{{ officer.id }}">
+
+            {{ officer }}
+            <form class="d-inline px-1"
+                  hx-post="{% url 'production:_hx_team_delete_officer' officer.id %}"
+                  hx-target="#production-team-officer-{{ officer.id }}">
+                {% csrf_token %}
+                <input type="submit" class="btn btn-danger mb-1" value="Remove Officer">
+            </form>
+        </li>
+    {% endfor %}
+</ul>
diff --git a/scipost_django/production/templates/production/upload_proofs.html b/scipost_django/production/templates/production/upload_proofs.html
index a4ac83767db90cb903e9bd181de1af2479f04c6a..afa710d9b9a743eb84cffd2967076dac34d7b02a 100644
--- a/scipost_django/production/templates/production/upload_proofs.html
+++ b/scipost_django/production/templates/production/upload_proofs.html
@@ -1,29 +1,27 @@
-{% extends 'production/base.html' %}
-
-{% block breadcrumb_items %}
-  {{block.super}}
-  <span class="breadcrumb-item">Upload Proofs</span>
-{% endblock %}
-
 {% load bootstrap %}
 
-{% 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>
+<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>
 
-{% endblock content %}
+<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>
+  </div>
+</div>
diff --git a/scipost_django/production/urls.py b/scipost_django/production/urls.py
index dd60eae857f5d1226349dffd72e640a3bd082f26..81e04bc2e833da0da4b1dbdd2c9a1ce49bba0bd0 100644
--- a/scipost_django/production/urls.py
+++ b/scipost_django/production/urls.py
@@ -15,11 +15,43 @@ urlpatterns = [
         production_views.production_new,
         name="production_new",
     ),
+    path(
+        "team",
+        include(
+            [
+                path(
+                    "",
+                    production_views.production_team,
+                    name="production_team",
+                ),
+                path(
+                    "list",
+                    production_views.production_team_list,
+                    name="production_team_list",
+                ),
+                path(
+                    "_hx_delete_officer/<int:officer_id>/",
+                    production_views._hx_team_delete_officer,
+                    name="_hx_team_delete_officer",
+                ),
+                path(
+                    "_hx_promote_user",
+                    production_views._hx_team_promote_user,
+                    name="_hx_team_promote_user",
+                ),
+            ]
+        ),
+    ),
     path(
         "_hx_productionstream_list",
         production_views._hx_productionstream_list,
         name="_hx_productionstream_list",
     ),
+    path(
+        "_hx_productionstream_actions_bulk_assign_officers",
+        production_views._hx_productionstream_actions_bulk_assign_officers,
+        name="_hx_productionstream_actions_bulk_assign_officers",
+    ),
     path(
         "productionstreams/<int:productionstream_id>/",
         include(
@@ -29,6 +61,26 @@ urlpatterns = [
                     production_views._hx_productionstream_details_contents,
                     name="_hx_productionstream_details_contents",
                 ),
+                path(
+                    "actions_change_properties",
+                    production_views._hx_productionstream_actions_change_properties,
+                    name="_hx_productionstream_actions_change_properties",
+                ),
+                path(
+                    "actions_work_log",
+                    production_views._hx_productionstream_actions_work_log,
+                    name="_hx_productionstream_actions_work_log",
+                ),
+                path(
+                    "_hx_productionstream_change_action_buttons/<str:key>",
+                    production_views._hx_productionstream_change_action_buttons,
+                    name="_hx_productionstream_change_action_buttons",
+                ),
+                path(
+                    "_hx_productionstream_summary_assignees_status",
+                    production_views._hx_productionstream_summary_assignees_status,
+                    name="_hx_productionstream_summary_assignees_status",
+                ),
                 path(
                     "events/",
                     include(
@@ -38,6 +90,11 @@ urlpatterns = [
                                 production_views._hx_event_form,
                                 name="_hx_event_form",
                             ),
+                            path(
+                                "list",
+                                production_views._hx_event_list,
+                                name="_hx_event_list",
+                            ),
                             path(
                                 "<int:event_id>/",
                                 include(
@@ -71,7 +128,6 @@ urlpatterns = [
         production_views.delete_officer,
         name="delete_officer",
     ),
-    # streams
     path(
         "streams/<int:stream_id>/",
         include(
@@ -99,16 +155,31 @@ urlpatterns = [
                                             production_views.decision,
                                             name="decision",
                                         ),
+                                        re_path(
+                                            "_hx_proofs_decision/(?P<decision>accept|decline)$",
+                                            production_views._hx_proofs_decision,
+                                            name="_hx_proofs_decision",
+                                        ),
                                         path(
                                             "send_to_authors",
                                             production_views.send_proofs,
                                             name="send_proofs",
                                         ),
+                                        path(
+                                            "_hx_send_to_authors",
+                                            production_views._hx_send_proofs,
+                                            name="_hx_send_proofs",
+                                        ),
                                         path(
                                             "toggle_access",
                                             production_views.toggle_accessibility,
                                             name="toggle_accessibility",
                                         ),
+                                        path(
+                                            "_hx_toggle_access",
+                                            production_views._hx_toggle_accessibility,
+                                            name="_hx_toggle_accessibility",
+                                        ),
                                     ]
                                 ),
                             ),
@@ -122,31 +193,71 @@ urlpatterns = [
                 ),
                 path("events/add", production_views.add_event, name="add_event"),
                 path("logs/add", production_views.add_work_log, name="add_work_log"),
-                path("officer/add", production_views.add_officer, name="add_officer"),
-                path(
-                    "officer/<int:officer_id>/remove",
-                    production_views.remove_officer,
-                    name="remove_officer",
-                ),
-                path(
-                    "invitations_officer/add",
-                    production_views.add_invitations_officer,
-                    name="add_invitations_officer",
-                ),
                 path(
-                    "invitations_officer/<int:officer_id>/remove",
-                    production_views.remove_invitations_officer,
-                    name="remove_invitations_officer",
+                    "officer",
+                    include(
+                        [
+                            path(
+                                "add",
+                                production_views.add_officer,
+                                name="add_officer",
+                            ),
+                            path(
+                                "<int:officer_id>/remove",
+                                production_views.remove_officer,
+                                name="remove_officer",
+                            ),
+                            path(
+                                "update",
+                                production_views.update_officer,
+                                name="update_officer",
+                            ),
+                        ]
+                    ),
                 ),
                 path(
-                    "supervisor/add",
-                    production_views.add_supervisor,
-                    name="add_supervisor",
+                    "invitations_officer",
+                    include(
+                        [
+                            path(
+                                "add",
+                                production_views.add_invitations_officer,
+                                name="add_invitations_officer",
+                            ),
+                            path(
+                                "<int:officer_id>/remove",
+                                production_views.remove_invitations_officer,
+                                name="remove_invitations_officer",
+                            ),
+                            path(
+                                "update",
+                                production_views.update_invitations_officer,
+                                name="update_invitations_officer",
+                            ),
+                        ]
+                    ),
                 ),
                 path(
-                    "supervisor/<int:officer_id>/remove",
-                    production_views.remove_supervisor,
-                    name="remove_supervisor",
+                    "supervisor",
+                    include(
+                        [
+                            path(
+                                "add",
+                                production_views.add_supervisor,
+                                name="add_supervisor",
+                            ),
+                            path(
+                                "<int:officer_id>/remove",
+                                production_views.remove_supervisor,
+                                name="remove_supervisor",
+                            ),
+                            path(
+                                "update",
+                                production_views.update_supervisor,
+                                name="update_supervisor",
+                            ),
+                        ]
+                    ),
                 ),
                 path(
                     "mark_completed",
diff --git a/scipost_django/production/views.py b/scipost_django/production/views.py
index f546f01b61bf9db50f28d14164be0b9634e6f25f..679b5770823d319f0b638c455cf677a6eed8fa03 100644
--- a/scipost_django/production/views.py
+++ b/scipost_django/production/views.py
@@ -20,7 +20,8 @@ from guardian.core import ObjectPermissionChecker
 from guardian.shortcuts import assign_perm, remove_perm
 
 from finances.forms import WorkLogForm
-from mails.views import MailEditorSubview
+from mails.views import MailEditorSubviewHTMX
+from scipost.views import HTMXPermissionsDenied, HTMXResponse
 
 from . import constants
 from .models import (
@@ -31,6 +32,7 @@ from .models import (
     ProductionEventAttachment,
 )
 from .forms import (
+    BulkAssignOfficersForm,
     ProductionStreamSearchForm,
     ProductionEventForm,
     ProductionEventForm_deprec,
@@ -42,7 +44,7 @@ from .forms import (
     ProofsDecisionForm,
     AssignInvitationsOfficerForm,
 )
-from .permissions import is_production_user
+from .permissions import is_production_user, permission_required_htmx
 from .utils import proofs_slug_to_id, ProductionUtils
 
 
@@ -54,15 +56,23 @@ from .utils import proofs_slug_to_id, ProductionUtils
 @is_production_user()
 @permission_required("scipost.can_view_production", raise_exception=True)
 def production_new(request):
-    form = ProductionStreamSearchForm(user=request.user)
-    context = {"search_productionstreams_form": form,}
+    search_productionstreams_form = ProductionStreamSearchForm(
+        user=request.user, session_key=request.session.session_key
+    )
+    bulk_assign_officer_form = BulkAssignOfficersForm()
+    context = {
+        "search_productionstreams_form": search_productionstreams_form,
+        "bulk_assign_officer_form": bulk_assign_officer_form,
+    }
     return render(request, "production/production_new.html", context)
 
 
 @is_production_user()
 @permission_required("scipost.can_view_production", raise_exception=True)
 def _hx_productionstream_list(request):
-    form = ProductionStreamSearchForm(request.POST or None, user=request.user)
+    form = ProductionStreamSearchForm(
+        request.POST or None, user=request.user, session_key=request.session.session_key
+    )
     if form.is_valid():
         streams = form.search_results()
     else:
@@ -72,7 +82,11 @@ def _hx_productionstream_list(request):
     page_obj = paginator.get_page(page_nr)
     count = paginator.count
     start_index = page_obj.start_index
-    context = {"count": count, "page_obj": page_obj, "start_index": start_index,}
+    context = {
+        "count": count,
+        "page_obj": page_obj,
+        "start_index": start_index,
+    }
     return render(request, "production/_hx_productionstream_list.html", context)
 
 
@@ -80,8 +94,33 @@ def _hx_productionstream_list(request):
 @permission_required("scipost.can_view_production", raise_exception=True)
 def _hx_productionstream_details_contents(request, productionstream_id):
     productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
+
+    # Determine which accordion tab to open by default
+    accordion_default_open = ""
+
+    if request.user.has_perm("scipost.can_assign_production_supervisor"):
+        accordion_default_open = "change-properties"
+
+    if request.user.production_user == productionstream.supervisor:
+        if productionstream.status in [
+            constants.PROOFS_ACCEPTED,
+        ]:
+            accordion_default_open = "upload-proofs"
+        else:
+            accordion_default_open = "change-properties"
+
+    if request.user.production_user == productionstream.officer:
+        if not productionstream.work_logs.all():
+            accordion_default_open = "work-log"
+        elif not productionstream.proofs.all():
+            accordion_default_open = "upload-proofs"
+
+    if productionstream.status == constants.PRODUCTION_STREAM_INITIATED:
+        accordion_default_open = "change-properties"
+
     context = {
         "productionstream": productionstream,
+        "accordion_default_open": accordion_default_open,
     }
     return render(
         request,
@@ -90,6 +129,43 @@ def _hx_productionstream_details_contents(request, productionstream_id):
     )
 
 
+@is_production_user()
+@permission_required("scipost.can_view_production", raise_exception=True)
+def _hx_productionstream_actions_change_properties(request, productionstream_id):
+    productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
+
+    status_form = StreamStatusForm(
+        instance=productionstream,
+        production_user=request.user.production_user,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
+    )
+    supervisor_form = AssignSupervisorForm(
+        instance=productionstream,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
+    )
+    invitations_officer_form = AssignInvitationsOfficerForm(
+        instance=productionstream,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
+    )
+    officer_form = AssignOfficerForm(
+        instance=productionstream,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
+    )
+
+    context = {
+        "productionstream": productionstream,
+        "status_form": status_form,
+        "supervisor_form": supervisor_form,
+        "officer_form": officer_form,
+        "invitations_officer_form": invitations_officer_form,
+    }
+    return render(
+        request,
+        "production/_hx_productionstream_actions_change_properties.html",
+        context,
+    )
+
+
 @is_production_user()
 @permission_required("scipost.can_view_production", raise_exception=True)
 def _hx_event_form(request, productionstream_id, event_id=None):
@@ -105,10 +181,14 @@ def _hx_event_form(request, productionstream_id, event_id=None):
         form = ProductionEventForm(request.POST, instance=productionevent)
         if form.is_valid():
             form.save()
-            return redirect(reverse(
-                "production:_hx_productionstream_details_contents",
-                kwargs={"productionstream_id": productionstream.id,},
-            ))
+            return redirect(
+                reverse(
+                    "production:_hx_productionstream_details_contents",
+                    kwargs={
+                        "productionstream_id": productionstream.id,
+                    },
+                )
+            )
     elif productionevent:
         form = ProductionEventForm(instance=productionevent)
     else:
@@ -130,10 +210,14 @@ def _hx_event_form(request, productionstream_id, event_id=None):
 def _hx_event_delete(request, productionstream_id, event_id):
     productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
     ProductionEvent.objects.filter(pk=event_id).delete()
-    return redirect(reverse(
-        "production:_hx_productionstream_details_contents",
-        kwargs={"productionstream_id": productionstream.id,},
-    ))
+    return redirect(
+        reverse(
+            "production:_hx_productionstream_details_contents",
+            kwargs={
+                "productionstream_id": productionstream.id,
+            },
+        )
+    )
 
 
 ################################
@@ -215,7 +299,7 @@ def stream(request, stream_id):
     if not request.user.has_perm("scipost.can_view_all_production_streams"):
         # Restrict stream queryset if user is not supervisor
         streams = streams.filter_for_user(request.user.production_user)
-    stream = get_object_or_404(streams, id=stream_id)
+    productionstream = get_object_or_404(streams, id=stream_id)
     prodevent_form = ProductionEventForm_deprec()
     assign_officer_form = AssignOfficerForm()
     assign_invitiations_officer_form = AssignInvitationsOfficerForm()
@@ -228,11 +312,13 @@ def stream(request, stream_id):
         types = constants.PRODUCTION_OFFICERS_WORK_LOG_TYPES
     work_log_form = WorkLogForm(log_types=types)
     status_form = StreamStatusForm(
-        instance=stream, production_user=request.user.production_user
+        instance=productionstream,
+        production_user=request.user.production_user,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
     )
 
     context = {
-        "stream": stream,
+        "stream": productionstream,
         "prodevent_form": prodevent_form,
         "assign_officer_form": assign_officer_form,
         "assign_supervisor_form": assign_supervisor_form,
@@ -266,40 +352,22 @@ def user_to_officer(request):
 
 @is_production_user()
 @permission_required("scipost.can_promote_user_to_production_officer")
-def delete_officer(request, officer_id):
-    production_user = get_object_or_404(ProductionUser.objects.active(), id=officer_id)
-    production_user.name = "{first_name} {last_name}".format(
-        first_name=production_user.user.first_name,
-        last_name=production_user.user.last_name,
-    )
-    production_user.user = None
-    production_user.save()
+def _hx_team_promote_user(request):
+    form = UserToOfficerForm(request.POST or None)
+    if form.is_valid():
+        if (officer := form.save()) and (user := getattr(officer, "user", None)):
+            # Add permission group
+            group = Group.objects.get(name="Production Officers")
+            user.groups.add(group)
+            messages.success(
+                request, "{user} promoted to Production Officer".format(user=officer)
+            )
 
-    messages.success(
-        request, "{user} removed as Production Officer".format(user=production_user)
+    return render(
+        request,
+        "production/_hx_team_promote_user.html",
+        {"form": form},
     )
-    return redirect(reverse("production:production"))
-
-
-@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()
@@ -347,6 +415,89 @@ def add_work_log(request, stream_id):
     return redirect(stream.get_absolute_url())
 
 
+@is_production_user()
+@permission_required_htmx(
+    ("scipost.can_view_production",),
+    message="You cannot view production.",
+)
+def _hx_productionstream_actions_work_log(request, productionstream_id):
+    productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm("can_work_for_stream", productionstream):
+        return HTMXPermissionsDenied("You cannot work in this stream.")
+
+    if request.user.has_perm("scipost.can_view_all_production_streams"):
+        types = constants.PRODUCTION_ALL_WORK_LOG_TYPES
+    else:
+        types = constants.PRODUCTION_OFFICERS_WORK_LOG_TYPES
+    work_log_form = WorkLogForm(request.POST or None, log_types=types)
+
+    if work_log_form.is_valid():
+        log = work_log_form.save(commit=False)
+        log.content = productionstream
+        log.user = request.user
+        log.save()
+        messages.success(request, "Work Log added to Stream.")
+
+    context = {
+        "productionstream": productionstream,
+        "work_log_form": work_log_form,
+    }
+
+    return render(
+        request,
+        "production/_hx_productionstream_actions_work_log.html",
+        context,
+    )
+
+
+@is_production_user()
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_take_decisions_related_to_proofs",
+    ),
+    message="You do not have permission to update the status of this stream.",
+    css_class="row",
+)
+def update_status(request, stream_id):
+    productionstream = get_object_or_404(
+        ProductionStream.objects.ongoing(), pk=stream_id
+    )
+
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm("can_perform_supervisory_actions", productionstream):
+        return HTMXPermissionsDenied(
+            "You cannot perform supervisory actions in this stream."
+        )
+
+    status_form = StreamStatusForm(
+        request.POST or None,
+        instance=productionstream,
+        production_user=request.user.production_user,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
+    )
+
+    if status_form.is_valid():
+        status_form.save()
+        status_form.fields["status"].choices = status_form.get_available_statuses()
+        messages.success(request, "Production Stream succesfully changed status.")
+
+    else:
+        messages.error(request, "\\n".join(status_form.errors.values()))
+
+    context = {
+        "stream": productionstream,
+        "form": status_form,
+    }
+
+    return render(
+        request,
+        "production/_hx_productionstream_change_status.html",
+        context,
+    )
+
+
 @is_production_user()
 @permission_required("scipost.can_assign_production_officer", raise_exception=True)
 @transaction.atomic
@@ -384,6 +535,138 @@ def add_officer(request, stream_id):
     return redirect(reverse("production:production", args=(stream.id,)))
 
 
+@is_production_user()
+@permission_required("scipost.can_assign_production_officer", raise_exception=True)
+@transaction.atomic
+def remove_officer(request, stream_id, officer_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,)))
+
+    if getattr(stream.officer, "id", 0) == int(officer_id):
+        officer = stream.officer
+        stream.officer = None
+        stream.save()
+        if officer not in [stream.invitations_officer, stream.supervisor]:
+            # Remove Officer from stream if not assigned anymore
+            remove_perm("can_work_for_stream", officer.user, stream)
+        messages.success(
+            request, "Officer {officer} has been removed.".format(officer=officer)
+        )
+
+    return redirect(reverse("production:production", args=(stream.id,)))
+
+
+@is_production_user()
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_assign_production_officer",
+    ),
+    message="You do not have permission to update the officer of this stream.",
+    css_class="row",
+)
+@transaction.atomic
+def update_officer(request, stream_id):
+    productionstream = get_object_or_404(
+        ProductionStream.objects.ongoing(), pk=stream_id
+    )
+    prev_officer = productionstream.officer
+
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm("can_perform_supervisory_actions", productionstream):
+        return HTMXPermissionsDenied(
+            "You cannot perform supervisory actions in this stream."
+        )
+
+    officer_form = AssignOfficerForm(
+        request.POST or None,
+        instance=productionstream,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
+    )
+
+    if officer_form.is_valid():
+        officer_form.save()
+        officer = officer_form.cleaned_data.get("officer")
+
+        # Add officer to stream if they exist.
+        if officer is not None:
+            assign_perm("can_work_for_stream", officer.user, productionstream)
+            messages.success(request, f"Officer {officer} has been assigned.")
+
+            event = ProductionEvent(
+                stream=productionstream,
+                event="assignment",
+                comments=" tasked Production Officer with proofs production:",
+                noted_to=officer,
+                noted_by=request.user.production_user,
+            )
+            event.save()
+
+            # Temp fix.
+            # TODO: Implement proper email
+            ProductionUtils.load({"request": request, "stream": productionstream})
+            ProductionUtils.email_assigned_production_officer()
+
+        # Remove old officer.
+        else:
+            remove_perm("can_work_for_stream", prev_officer.user, productionstream)
+            messages.success(request, "Officer {prev_officer} has been removed.")
+
+    else:
+        messages.error(request, "\\n".join(officer_form.errors.values()))
+
+    context = {
+        "stream": productionstream,
+        "form": officer_form,
+    }
+
+    return render(
+        request,
+        "production/_hx_productionstream_change_officer.html",
+        context,
+    )
+
+
+@is_production_user()
+@permission_required("scipost.can_promote_user_to_production_officer")
+def delete_officer(request, officer_id):
+    production_user = get_object_or_404(ProductionUser.objects.active(), id=officer_id)
+    production_user.name = "{first_name} {last_name}".format(
+        first_name=production_user.user.first_name,
+        last_name=production_user.user.last_name,
+    )
+    production_user.user = None
+    production_user.save()
+
+    messages.success(
+        request, "{user} removed as Production Officer".format(user=production_user)
+    )
+    return redirect(reverse("production:production"))
+
+
+@is_production_user()
+@permission_required("scipost.can_promote_user_to_production_officer")
+def _hx_team_delete_officer(request, officer_id):
+    production_user = get_object_or_404(ProductionUser.objects.active(), id=officer_id)
+    production_user.name = "{first_name} {last_name}".format(
+        first_name=production_user.user.first_name,
+        last_name=production_user.user.last_name,
+    )
+    production_user.user = None
+    production_user.save()
+
+    messages.success(
+        request, "{user} removed as Production Officer".format(user=production_user)
+    )
+
+    return HTMXResponse(
+        "Production Officer {user} has been removed.".format(user=production_user),
+        tag="danger",
+    )
+
+
 @is_production_user()
 @permission_required("scipost.can_assign_production_officer", raise_exception=True)
 @transaction.atomic
@@ -424,48 +707,98 @@ def add_invitations_officer(request, stream_id):
 @is_production_user()
 @permission_required("scipost.can_assign_production_officer", raise_exception=True)
 @transaction.atomic
-def remove_officer(request, stream_id, officer_id):
+def remove_invitations_officer(request, stream_id, officer_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,)))
 
-    if getattr(stream.officer, "id", 0) == int(officer_id):
-        officer = stream.officer
-        stream.officer = None
+    if getattr(stream.invitations_officer, "id", 0) == int(officer_id):
+        officer = stream.invitations_officer
+        stream.invitations_officer = None
         stream.save()
-        if officer not in [stream.invitations_officer, stream.supervisor]:
+        if officer not in [stream.officer, stream.supervisor]:
             # Remove Officer from stream if not assigned anymore
             remove_perm("can_work_for_stream", officer.user, stream)
         messages.success(
-            request, "Officer {officer} has been removed.".format(officer=officer)
+            request,
+            "Invitations Officer {officer} has been removed.".format(officer=officer),
         )
 
     return redirect(reverse("production:production", args=(stream.id,)))
 
 
 @is_production_user()
-@permission_required("scipost.can_assign_production_officer", raise_exception=True)
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_assign_production_officer",
+    ),
+    message="You do not have permission to update the invitations officer of this stream.",
+    css_class="row",
+)
 @transaction.atomic
-def remove_invitations_officer(request, stream_id, officer_id):
-    stream = get_object_or_404(ProductionStream.objects.ongoing(), pk=stream_id)
+def update_invitations_officer(request, stream_id):
+    productionstream = get_object_or_404(
+        ProductionStream.objects.ongoing(), pk=stream_id
+    )
+    prev_inv_officer = productionstream.invitations_officer
     checker = ObjectPermissionChecker(request.user)
-    if not checker.has_perm("can_perform_supervisory_actions", stream):
-        return redirect(reverse("production:production", args=(stream.id,)))
 
-    if getattr(stream.invitations_officer, "id", 0) == int(officer_id):
-        officer = stream.invitations_officer
-        stream.invitations_officer = None
-        stream.save()
-        if officer not in [stream.officer, stream.supervisor]:
-            # Remove Officer from stream if not assigned anymore
-            remove_perm("can_work_for_stream", officer.user, stream)
-        messages.success(
-            request,
-            "Invitations Officer {officer} has been removed.".format(officer=officer),
+    if not checker.has_perm("can_perform_supervisory_actions", productionstream):
+        return HTMXPermissionsDenied(
+            "You cannot perform supervisory actions in this stream."
         )
 
-    return redirect(reverse("production:production", args=(stream.id,)))
+    invitations_officer_form = AssignInvitationsOfficerForm(
+        request.POST or None,
+        instance=productionstream,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
+    )
+    if invitations_officer_form.is_valid():
+        invitations_officer_form.save()
+        inv_officer = invitations_officer_form.cleaned_data.get("invitations_officer")
+
+        # Add invitations officer to stream if they exist.
+        if inv_officer is not None:
+            assign_perm("can_work_for_stream", inv_officer.user, productionstream)
+            messages.success(
+                request, f"Invitations Officer {inv_officer} has been assigned."
+            )
+
+            event = ProductionEvent(
+                stream=productionstream,
+                event="assignment",
+                comments=" tasked Invitations Officer with invitations:",
+                noted_to=inv_officer,
+                noted_by=request.user.production_user,
+            )
+            event.save()
+
+            # Temp fix.
+            # TODO: Implement proper email
+            ProductionUtils.load({"request": request, "stream": productionstream})
+            ProductionUtils.email_assigned_invitation_officer()
+
+        # Remove old invitations officer.
+        else:
+            remove_perm("can_work_for_stream", prev_inv_officer.user, productionstream)
+            messages.success(
+                request, f"Invitations Officer {prev_inv_officer} has been removed."
+            )
+    else:
+        messages.error(request, "\\n".join(invitations_officer_form.errors.values()))
+
+    context = {
+        "stream": productionstream,
+        "form": invitations_officer_form,
+    }
+
+    return render(
+        request,
+        "production/_hx_productionstream_change_invitations_officer.html",
+        context,
+    )
 
 
 @is_production_user()
@@ -542,6 +875,76 @@ class UpdateEventView(UpdateView):
         return super().form_valid(form)
 
 
+@is_production_user()
+@permission_required_htmx(
+    ("scipost.can_view_production", "scipost.can_assign_production_supervisor"),
+    message="You do not have permission to update the supervisor of this stream.",
+    css_class="row",
+)
+@transaction.atomic
+def update_supervisor(request, stream_id):
+    productionstream = get_object_or_404(
+        ProductionStream.objects.ongoing(), pk=stream_id
+    )
+    supervisor_form = AssignSupervisorForm(
+        request.POST or None,
+        instance=productionstream,
+        auto_id=f"productionstream_{productionstream.id}_id_%s",
+    )
+    prev_supervisor = productionstream.supervisor
+
+    if supervisor_form.is_valid():
+        supervisor_form.save()
+        supervisor = supervisor_form.cleaned_data.get("supervisor")
+
+        # Add supervisor to stream if they exist.
+        if supervisor is not None:
+            messages.success(request, f"Supervisor {supervisor} has been assigned.")
+
+            assign_perm("can_work_for_stream", supervisor.user, productionstream)
+            assign_perm(
+                "can_perform_supervisory_actions", supervisor.user, productionstream
+            )
+
+            event = ProductionEvent(
+                stream=productionstream,
+                event="assignment",
+                comments=" assigned Production Supervisor:",
+                noted_to=supervisor,
+                noted_by=request.user.production_user,
+            )
+            event.save()
+
+            # Temp fix.
+            # TODO: Implement proper email
+            ProductionUtils.load({"request": request, "stream": productionstream})
+            ProductionUtils.email_assigned_supervisor()
+
+        # Remove old supervisor.
+        else:
+            remove_perm("can_work_for_stream", prev_supervisor.user, productionstream)
+            remove_perm(
+                "can_perform_supervisory_actions",
+                prev_supervisor.user,
+                productionstream,
+            )
+            messages.success(request, f"Supervisor {prev_supervisor} has been removed.")
+
+    else:
+        messages.error(request, "\\n".join(supervisor_form.errors.values()))
+
+    context = {
+        "stream": productionstream,
+        "form": supervisor_form,
+    }
+
+    return render(
+        request,
+        "production/_hx_productionstream_change_supervisor.html",
+        context,
+    )
+
+
 @method_decorator(is_production_user(), name="dispatch")
 @method_decorator(
     permission_required("scipost.can_view_production", raise_exception=True),
@@ -567,24 +970,41 @@ class DeleteEventView(DeleteView):
 @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()
+    productionstream = get_object_or_404(
+        ProductionStream.objects.ongoing(), pk=stream_id
+    )
+    productionstream.status = constants.PRODUCTION_STREAM_COMPLETED
+    productionstream.closed = timezone.now()
+    productionstream.save()
 
-    prodevent = ProductionEvent(
-        stream=stream,
+    production_event = ProductionEvent(
+        stream=productionstream,
         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"))
+    production_event.save()
+
+    messages.success(
+        request,
+        "Production Stream has been marked as completed.",
+    )
+
+    return HttpResponse(
+        r"""<summary class="text-white bg-success summary-unstyled p-3">
+                Production Stream has been marked as completed.
+            </summary>"""
+    )
 
 
 @is_production_user()
-@permission_required("scipost.can_upload_proofs", raise_exception=True)
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_upload_proofs",
+    ),
+    message="You cannot upload proofs for this stream.",
+)
 @transaction.atomic
 def upload_proofs(request, stream_id):
     """
@@ -592,9 +1012,10 @@ def upload_proofs(request, stream_id):
     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"))
+        return HTMXPermissionsDenied("You cannot work in this stream.")
 
     form = ProofsUploadForm(request.POST or None, request.FILES or None)
     if form.is_valid():
@@ -605,7 +1026,6 @@ def upload_proofs(request, stream_id):
         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:
@@ -622,9 +1042,8 @@ def upload_proofs(request, stream_id):
             noted_by=request.user.production_user,
         )
         prodevent.save()
-        return redirect(stream.get_absolute_url())
 
-    context = {"stream": stream, "form": form}
+    context = {"stream": stream, "form": form, "total_proofs": stream.proofs.count()}
     return render(request, "production/upload_proofs.html", context)
 
 
@@ -649,6 +1068,7 @@ def proofs(request, stream_id, version):
     return render(request, "production/proofs.html", context)
 
 
+@permission_required("scipost.can_view_production", raise_exception=True)
 def proofs_pdf(request, slug):
     """Open Proofs pdf."""
     if not request.user.is_authenticated:
@@ -659,14 +1079,12 @@ def proofs_pdf(request, slug):
     proofs = get_object_or_404(Proofs, id=proofs_slug_to_id(slug))
     stream = proofs.stream
 
-    # Check if user has access!
+    # Check if user has access!η
     checker = ObjectPermissionChecker(request.user)
-    access = checker.has_perm("can_work_for_stream", stream) and request.user.has_perm(
-        "scipost.can_view_production"
-    )
-    if not access and request.user.contributor:
-        access = request.user.contributor in proofs.stream.submission.authors.all()
-    if not access:
+    can_work_for_stream = checker.has_perm("can_work_for_stream", stream)
+    is_submission_author = request.user.contributor in stream.submission.authors.all()
+
+    if not (can_work_for_stream or is_submission_author):
         raise Http404
 
     # Passed the test! The user may see the file!
@@ -754,7 +1172,47 @@ def toggle_accessibility(request, stream_id, version):
     proofs.accessible_for_authors = not proofs.accessible_for_authors
     proofs.save()
     messages.success(request, "Proofs accessibility updated.")
-    return redirect(stream.get_absolute_url())
+    return redirect(reverse("production:proofs", args=(stream.id, proofs.version)))
+
+
+@is_production_user()
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_take_decisions_related_to_proofs",
+        "scipost.can_run_proofs_by_authors",
+    ),
+    message="You cannot make proofs accessible to the authors.",
+)
+def _hx_toggle_accessibility(request, stream_id, version):
+    """
+    Open/close accessibility of proofs to the authors.
+    """
+    stream = get_object_or_404(ProductionStream.objects.all(), pk=stream_id)
+    checker = ObjectPermissionChecker(request.user)
+    if not checker.has_perm("can_work_for_stream", stream):
+        return HTMXPermissionsDenied("You cannot work in this stream.")
+
+    try:
+        proofs = stream.proofs.exclude(status=constants.PROOFS_UPLOADED).get(
+            version=version
+        )
+    except Proofs.DoesNotExist:
+        return HTMXResponse("Proofs do not exist.", tag="danger")
+
+    proofs.accessible_for_authors = not proofs.accessible_for_authors
+    proofs.save()
+    messages.success(request, "Proofs accessibility updated.")
+    return render(
+        request,
+        "production/_hx_productionstream_actions_proofs_item.html",
+        {
+            "stream": stream,
+            "proofs": proofs,
+            "total_proofs": stream.proofs.count(),
+            "active_id": proofs.version,
+        },
+    )
 
 
 @is_production_user()
@@ -799,6 +1257,65 @@ def decision(request, stream_id, version, decision):
     return redirect(reverse("production:proofs", args=(stream.id, proofs.version)))
 
 
+@is_production_user()
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_take_decisions_related_to_proofs",
+        "scipost.can_run_proofs_by_authors",
+    ),
+    message="You cannot accept or decline proofs.",
+)
+@transaction.atomic
+def _hx_proofs_decision(request, stream_id, version, decision):
+    """
+    Send/open proofs to the authors. This decision is taken by the supervisor.
+    """
+    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 HTMXPermissionsDenied("You cannot work in this stream.")
+
+    try:
+        proofs = stream.proofs.get(version=version, status=constants.PROOFS_UPLOADED)
+    except Proofs.DoesNotExist:
+        return HTMXResponse("Proofs do not exist.", tag="danger")
+
+    if decision == "accept":
+        stream.status = constants.PROOFS_CHECKED
+        proofs.status = constants.PROOFS_ACCEPTED_SUP
+        decision = "accepted"
+    else:
+        stream.status = constants.PROOFS_TASKED
+        proofs.status = constants.PROOFS_DECLINED_SUP
+        proofs.accessible_for_authors = False
+        decision = "declined"
+
+    stream.save()
+    proofs.save()
+
+    prodevent = ProductionEvent(
+        stream=stream,
+        event="status",
+        comments="Proofs version {version} are {decision}.".format(
+            version=proofs.version, decision=decision
+        ),
+        noted_by=request.user.production_user,
+    )
+    prodevent.save()
+    messages.success(request, f"Proofs have been {decision}.")
+    return render(
+        request,
+        "production/_hx_productionstream_actions_proofs_item.html",
+        {
+            "stream": stream,
+            "proofs": proofs,
+            "total_proofs": stream.proofs.count(),
+            "active_id": proofs.version,
+        },
+    )
+
+
 @is_production_user()
 @permission_required("scipost.can_run_proofs_by_authors", raise_exception=True)
 @transaction.atomic
@@ -827,7 +1344,9 @@ def send_proofs(request, stream_id, version):
         stream.status = constants.PROOFS_SENT
         stream.save()
 
-    mail_request = MailEditorSubview(request, "production_send_proofs", proofs=proofs)
+    mail_request = MailEditorSubviewHTMX(
+        request, "production_send_proofs", proofs=proofs
+    )
     if mail_request.is_valid():
         proofs.save()
         stream.save()
@@ -848,3 +1367,286 @@ def send_proofs(request, stream_id, version):
 
     messages.success(request, "Proofs have been sent.")
     return redirect(stream.get_absolute_url())
+
+
+@is_production_user()
+@permission_required_htmx(
+    (
+        "scipost.can_view_production",
+        "scipost.can_take_decisions_related_to_proofs",
+        "scipost.can_run_proofs_by_authors",
+    ),
+    message="You cannot send proofs to the authors.",
+)
+@transaction.atomic
+def _hx_send_proofs(request, stream_id, version):
+    """
+    Send/open proofs to the authors.
+    """
+    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 HTMXPermissionsDenied("You cannot work in this stream.")
+
+    try:
+        proofs = stream.proofs.can_be_send().get(version=version)
+    except Proofs.DoesNotExist:
+        return HTMXResponse("Proofs do not exist.", tag="danger")
+
+    proofs.status = constants.PROOFS_SENT
+    proofs.accessible_for_authors = True
+
+    if stream.status not in [
+        constants.PROOFS_PUBLISHED,
+        constants.PROOFS_CITED,
+        constants.PRODUCTION_STREAM_COMPLETED,
+    ]:
+        stream.status = constants.PROOFS_SENT
+        stream.save()
+
+    mail_request = MailEditorSubviewHTMX(
+        request,
+        "production_send_proofs",
+        proofs=proofs,
+        context={
+            "view_url": reverse("production:_hx_send_proofs", args=[stream_id, version])
+        },
+    )
+
+    print(request)
+
+    if request.method == "GET":
+        return mail_request.interrupt()
+
+    if mail_request.is_valid():
+        proofs.save()
+        stream.save()
+
+        mail_request.send_mail()
+
+        prodevent = ProductionEvent(
+            stream=stream,
+            event="status",
+            comments="Proofs version {version} sent to authors.".format(
+                version=proofs.version
+            ),
+            noted_by=request.user.production_user,
+        )
+        prodevent.save()
+
+        messages.success(request, "Proofs have been sent.")
+        return HTMXResponse("Proofs have been sent to the authors.", tag="success")
+    else:
+        messages.error(request, "Proofs have not been sent.")
+        return HTMXResponse(
+            "Proofs have not been sent. Please check the form.", tag="danger"
+        )
+
+
+def _hx_productionstream_change_action_buttons(request, productionstream_id, key):
+    productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
+
+    # Get either the id, or the id of the object and convert it to a string
+    # If this fails, set to "None"
+    current_option = getattr(productionstream, key, None)
+    current_option = getattr(current_option, "id", current_option)
+    current_option_str = str(current_option)
+
+    # Get the new option from the POST request, which is a string
+    # Set to "None" if the string is empty
+    new_option = request.POST.get(key, None)
+    new_option_str = str(new_option) or "None"
+
+    return render(
+        request,
+        "production/_hx_productionstream_change_action_buttons.html",
+        {
+            "current_option": current_option_str,
+            "new_option": new_option_str,
+        },
+    )
+
+
+def _hx_productionstream_summary_assignees_status(request, productionstream_id):
+    productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
+
+    context = {
+        "productionstream": productionstream,
+    }
+
+    return render(
+        request,
+        "production/_hx_productionstream_summary_assignees_status.html",
+        context,
+    )
+
+
+def _hx_event_list(request, productionstream_id):
+    productionstream = get_object_or_404(ProductionStream, pk=productionstream_id)
+
+    context = {
+        "productionstream": productionstream,
+        "events": productionstream.events.all_without_duration,
+    }
+
+    return render(
+        request,
+        "production/_productionstream_events.html",
+        context,
+    )
+
+
+def _hx_productionstream_actions_bulk_assign_officers(request):
+    if request.POST:
+        productionstream_ids = (
+            request.POST.getlist("productionstream-bulk-action-selected") or []
+        )
+        productionstreams = ProductionStream.objects.filter(pk__in=productionstream_ids)
+
+        form = BulkAssignOfficersForm(
+            request.POST,
+            productionstreams=productionstreams,
+            auto_id="productionstreams-bulk-action-form-%s",
+        )
+
+    else:
+        form = BulkAssignOfficersForm()
+
+    # Render the form if it is not valid (usually when post data is missing)
+    if not form.is_valid():
+        return render(
+            request,
+            "production/_hx_productionstream_actions_bulk_assign_officer.html",
+            {"form": form},
+        )
+
+    if officer := form.cleaned_data["officer"]:
+        if not request.user.has_perm("scipost.can_assign_production_officer"):
+            messages.error(
+                request, "You do not have permission to assign officers to streams."
+            )
+        else:
+            # Create events, update permissions, send emails for each stream
+            for productionstream in form.productionstreams:
+                old_officer = productionstream.officer
+
+                if old_officer == officer:
+                    continue
+
+                if productionstream.status in [
+                    constants.PRODUCTION_STREAM_INITIATED,
+                    constants.PROOFS_SOURCE_REQUESTED,
+                ]:
+                    productionstream.status = constants.PROOFS_TASKED
+
+                productionstream.officer = officer
+                productionstream.save()
+
+                event = ProductionEvent(
+                    stream=productionstream,
+                    event="assignment",
+                    comments=" tasked Production Officer with proofs production:",
+                    noted_to=officer,
+                    noted_by=request.user.production_user,
+                )
+                event.save()
+
+                # Update permissions
+                assign_perm("can_work_for_stream", officer.user, productionstream)
+                if old_officer is not None:
+                    remove_perm(
+                        "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()
+
+            messages.success(
+                request,
+                f"Assigned {officer} as production officer to the selected streams.",
+            )
+
+    if (supervisor := form.cleaned_data["supervisor"]) and not request.user.has_perm(
+        "production.can_perform_supervisory_actions"
+    ):
+        messages.error(
+            request, "You do not have permission to assign supervisors to streams."
+        )
+    elif supervisor is not None:
+        for productionstream in form.productionstreams:
+            old_supervisor = productionstream.supervisor
+
+            if old_supervisor == supervisor:
+                continue
+
+            productionstream.supervisor = supervisor
+            productionstream.save()
+
+            event = ProductionEvent(
+                stream=productionstream,
+                event="assignment",
+                comments=" assigned Production Supervisor:",
+                noted_to=supervisor,
+                noted_by=request.user.production_user,
+            )
+            event.save()
+
+            # Update permissions
+            assign_perm("can_work_for_stream", supervisor.user, productionstream)
+            assign_perm(
+                "can_perform_supervisory_actions",
+                supervisor.user,
+                productionstream,
+            )
+            if old_supervisor is not None:
+                remove_perm(
+                    "can_work_for_stream", old_supervisor.user, productionstream
+                )
+                remove_perm(
+                    "can_perform_supervisory_actions",
+                    old_supervisor.user,
+                    productionstream,
+                )
+
+            # Temp fix.
+            # TODO: Implement proper email
+            ProductionUtils.load({"request": request, "stream": productionstream})
+            ProductionUtils.email_assigned_supervisor()
+
+        messages.success(
+            request,
+            f"Assigned {supervisor} as supervisor to the selected streams.",
+        )
+
+    return render(
+        request,
+        "production/_hx_productionstream_actions_bulk_assign_officer.html",
+        {"form": form},
+    )
+
+
+@permission_required(
+    "scipost.can_promote_user_to_production_officer", raise_exception=True
+)
+def production_team(request):
+    context = {
+        "production_officers": ProductionUser.objects.active().filter(
+            user__groups__name="Production Officers"
+        ),
+        "new_officer_form": UserToOfficerForm(),
+    }
+    return render(request, "production/production_team.html", context)
+
+
+@permission_required(
+    "scipost.can_promote_user_to_production_officer", raise_exception=True
+)
+def production_team_list(request):
+    context = {
+        "officers": ProductionUser.objects.active().filter(
+            user__groups__name="Production Officers"
+        ),
+    }
+    return render(request, "production/production_team_list.html", context)
diff --git a/scipost_django/scipost/static/scipost/assets/config/preconfig.scss b/scipost_django/scipost/static/scipost/assets/config/preconfig.scss
index c57b7db8d35bb2d19a6de42a747307b3045f7bdc..56124499ce0e1c769f7be5d798e68acf7c3e6052 100644
--- a/scipost_django/scipost/static/scipost/assets/config/preconfig.scss
+++ b/scipost_django/scipost/static/scipost/assets/config/preconfig.scss
@@ -200,3 +200,24 @@ $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 {
+  display: none;
+}
+
+.checkbox-lg {
+  width: 1.5em !important;
+  height: 1.5em !important;
+}
\ No newline at end of file
diff --git a/scipost_django/scipost/static/scipost/assets/css/_messages.scss b/scipost_django/scipost/static/scipost/assets/css/_messages.scss
index 163fff905cf2989791c7faf58b749e41147dd8bf..3061f3da44f27e6340a0c40e6d12a22d60aaf398 100644
--- a/scipost_django/scipost/static/scipost/assets/css/_messages.scss
+++ b/scipost_django/scipost/static/scipost/assets/css/_messages.scss
@@ -3,8 +3,7 @@
     padding-right: 10px;
     position: fixed;
     bottom: 0px;
-    left: 0px;
-    width: 100%;
+    right: 0px;
     z-index: 9999;
 }
 
diff --git a/scipost_django/scipost/templates/scipost/_hx_messages.html b/scipost_django/scipost/templates/scipost/_hx_messages.html
new file mode 100644
index 0000000000000000000000000000000000000000..c19a73bb7e488e4d3a4ca3effe7cd2809119edd5
--- /dev/null
+++ b/scipost_django/scipost/templates/scipost/_hx_messages.html
@@ -0,0 +1,10 @@
+{% for message in messages %}
+  <div class="alert alert-{{ message.tags }} alert-dismissible" role="alert">
+    <button type="button"
+            class="btn-close"
+            style="top: unset !important"
+            data-bs-dismiss="alert"
+            aria-label="Close"></button>
+    {{ message|safe|escape }}
+  </div>
+{% endfor %}
diff --git a/scipost_django/scipost/templates/scipost/bare_base.html b/scipost_django/scipost/templates/scipost/bare_base.html
index 1840faaf4bfc8abfbc37d2930df7b9bd2c21affd..ae45a6e61f6cb0cd6f41926cebd45cbb4f1ba28e 100644
--- a/scipost_django/scipost/templates/scipost/bare_base.html
+++ b/scipost_django/scipost/templates/scipost/bare_base.html
@@ -34,6 +34,7 @@
     {% block breadcrumb %}{% endblock breadcrumb %}
 
     {% block secondary_navbar %}{% endblock secondary_navbar %}
+    
     {% include 'scipost/messages.html' %}
 
 
diff --git a/scipost_django/scipost/templates/scipost/messages.html b/scipost_django/scipost/templates/scipost/messages.html
index c48de0b27d90372952551f88ecd9e8288b6e40ca..47813aaf728064c46747a8992b6c379a6f0cd7b0 100644
--- a/scipost_django/scipost/templates/scipost/messages.html
+++ b/scipost_django/scipost/templates/scipost/messages.html
@@ -1,10 +1,6 @@
-<div class="alert-fixed-container">
-  {% for message in messages %}
-    <div class="alert alert-{{ message.tags }} alert-dismissible" role="alert">
-      <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
-        <span aria-hidden="true">&times;</span>
-      </button>
-      {{ message|safe|escape }}
-    </div>
-  {% endfor %}
-</div>
+<div id="global-message-container"
+     class="alert-fixed-container"
+     hx-get="{% url 'scipost:_hx_messages' %}"
+     hx-trigger="load, htmx:trigger from:body delay:1000"
+     hx-swap="beforeend"
+     hx-sync="body:drop"></div>
diff --git a/scipost_django/scipost/templatetags/bootstrap.py b/scipost_django/scipost/templatetags/bootstrap.py
index f255ebd9d8061546d689dc421151155d7156347c..ac204b325c8822d5a52b1ca623d1c9b3d1cb9943 100644
--- a/scipost_django/scipost/templatetags/bootstrap.py
+++ b/scipost_django/scipost/templatetags/bootstrap.py
@@ -60,6 +60,19 @@ def bootstrap_inline(element, args="2,10"):
     return render(element, markup_classes)
 
 
+@register.filter
+def bootstrap_purely_inline(element, args="2,10"):
+    args = [arg.strip() for arg in args.split(",")]
+    markup_classes = {
+        "label": "col-auto fs-6",
+        "value": "col",
+        "single_value": "",
+    }
+    markup_classes["form_control"] = ""
+
+    return render(element, markup_classes)
+
+
 @register.filter
 def bootstrap_grouped(element, args="2,10"):
     return bootstrap(element, args, "grouped")
diff --git a/scipost_django/scipost/templatetags/scipost_extras.py b/scipost_django/scipost/templatetags/scipost_extras.py
index 1e3eca861aca659d82e32c38b431431c272752ed..76c9f30aae17c08f176e63ed53e9d6a140a85d7a 100644
--- a/scipost_django/scipost/templatetags/scipost_extras.py
+++ b/scipost_django/scipost/templatetags/scipost_extras.py
@@ -2,6 +2,7 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
+from functools import reduce
 import random
 
 from django import template
@@ -97,3 +98,19 @@ def associated_contributors(draft):
     return Contributor.objects.filter(
         user__last_name__icontains=draft.last_name
     ).order_by("user__last_name")
+
+
+@register.filter
+def readable_str(value):
+    replacements = {
+        "_": " ",
+    }
+    return reduce(lambda a, kv: a.replace(*kv), replacements.items(), value)
+
+
+@register.filter
+def all_fields_have_choices(form):
+    for field in form.fields:
+        if len(form.fields[field].choices) == 0:
+            return False
+    return True
diff --git a/scipost_django/scipost/urls.py b/scipost_django/scipost/urls.py
index d82f43b8f9f6a6c9f3d54f78b55cba6fbd7b20a1..dabfb75dbd0459ac99b6b054773c2be2f303e0f6 100644
--- a/scipost_django/scipost/urls.py
+++ b/scipost_django/scipost/urls.py
@@ -156,6 +156,7 @@ urlpatterns = [
         TemplateView.as_view(template_name="scipost/acknowledgement.html"),
         name="acknowledgement",
     ),
+    path("messages", views._hx_messages, name="_hx_messages"),
     #
     #######
     # Info
diff --git a/scipost_django/scipost/views.py b/scipost_django/scipost/views.py
index 26ac38be5652e7e4e9be70434ac21efbc41978b2..923d154c9f0c1da64626af602ddb1c0afa8d100e 100644
--- a/scipost_django/scipost/views.py
+++ b/scipost_django/scipost/views.py
@@ -175,6 +175,37 @@ def trigger_error(request):
     division_by_zero = 1 / 0
 
 
+def _hx_messages(request):
+    return render(request, "scipost/_hx_messages.html")
+
+
+####################
+# HTMX inline alerts
+####################
+
+
+class HTMXResponse(HttpResponse):
+    tag = "primary"
+    message = ""
+    css_class = ""
+
+    def __init__(self, *args, **kwargs):
+        tag = kwargs.pop("tag", self.tag)
+        message = args[0] if args else kwargs.pop("message", self.message)
+        css_class = kwargs.pop("css_class", self.css_class)
+
+        alert_html = f"""<div class="text-{tag} border border-{tag} p-3 {css_class}">
+                {message}
+            </div>"""
+
+        super().__init__(alert_html, *args, **kwargs)
+
+
+class HTMXPermissionsDenied(HTMXResponse):
+    tag = "danger"
+    message = "You do not have the required permissions."
+
+
 #############
 # Main view
 #############
diff --git a/scipost_django/submissions/refereeing_cycles.py b/scipost_django/submissions/refereeing_cycles.py
index f29417eed5a91ae0bd563c27aef58220729d11c8..6e4fa88e01a088bf695c50617450b6e97687b4a2 100644
--- a/scipost_django/submissions/refereeing_cycles.py
+++ b/scipost_django/submissions/refereeing_cycles.py
@@ -8,8 +8,6 @@ from django.urls import reverse
 from django.utils import timezone
 from django.utils.html import format_html, format_html_join, html_safe
 
-from scipost.utils import build_absolute_uri_using_site
-
 from . import constants
 
 
@@ -125,11 +123,9 @@ class VettingAction(BaseAction):
     @property
     def url(self):
         return "{}#current-contributions".format(
-            build_absolute_uri_using_site(
-                reverse(
-                    "submissions:editorial_page",
-                    args=(self.submission.preprint.identifier_w_vn_nr,),
-                )
+            reverse(
+                "submissions:editorial_page",
+                args=(self.submission.preprint.identifier_w_vn_nr,),
             )
         )
 
@@ -181,21 +177,17 @@ class NoEICRecommendationAction(BaseAction):
     @property
     def url(self):
         return "{}#reporting-deadline".format(
-            build_absolute_uri_using_site(
-                reverse(
-                    "submissions:editorial_page",
-                    args=(self.submission.preprint.identifier_w_vn_nr,),
-                )
+            reverse(
+                "submissions:editorial_page",
+                args=(self.submission.preprint.identifier_w_vn_nr,),
             )
         )
 
     @property
     def url2(self):
-        return build_absolute_uri_using_site(
-            reverse(
-                "submissions:eic_recommendation",
-                args=(self.submission.preprint.identifier_w_vn_nr,),
-            )
+        reverse(
+            "submissions:eic_recommendation",
+            args=(self.submission.preprint.identifier_w_vn_nr,),
         )
 
 
@@ -218,11 +210,9 @@ class NeedRefereesAction(BaseAction):
             )
         text += ' At least {minimum} should be. <a href="{url}">Invite a referee here</a>.'.format(
             minimum=self.minimum_number_of_referees,
-            url=build_absolute_uri_using_site(
-                reverse(
-                    "submissions:select_referee",
-                    args=(self.submission.preprint.identifier_w_vn_nr,),
-                )
+            url=reverse(
+                "submissions:select_referee",
+                args=(self.submission.preprint.identifier_w_vn_nr,),
             ),
         )
         return text
diff --git a/scipost_django/templates/bi/arxiv.html b/scipost_django/templates/bi/arxiv.html
new file mode 100644
index 0000000000000000000000000000000000000000..be6d421bb165866b95994c04f3b9d588dad6a02c
--- /dev/null
+++ b/scipost_django/templates/bi/arxiv.html
@@ -0,0 +1,12 @@
+<svg width="8.47mm"
+     height="8.47mm"
+     version="1.1"
+     viewBox="0 0 8.47 8.47"
+     xmlns="http://www.w3.org/2000/svg">
+  <circle cx="4.23" cy="4.23" r="4.23" fill="#aa142d" stop-color="#000000" stroke-width="2" style="-inkscape-stroke:none" />
+  <g transform="scale(2)" stroke-width=".0284">
+  <path d="m2.09 2.13-0.93 1.1c-0.0365 0.039-0.0592 0.107-0.0388 0.156a0.134 0.134 0 0 0 0.125 0.0827 0.119 0.119 0 0 0 0.0897-0.0444l1.14-1.21a0.125 0.125 0 0 0 0.00119-0.172z" fill="#fff" />
+  <path d="m2.09 2.13 0.886-1.09c0.0424-0.0565 0.0625-0.086 0.0424-0.134a0.146 0.146 0 0 0-0.127-0.0898 0.114 0.114 0 0 0-0.0854 0.0315l-1.1 1.19a0.135 0.135 0 0 0 4.26e-4 0.185l1.36 1.45a0.111 0.111 0 0 0 0.0891 0.0338 0.125 0.125 0 0 0 0.114-0.08c0.0204-0.049-0.00216-0.0976-0.0398-0.149l-1.14-1.35" fill="#b3b3b3" />
+  <path d="m2.48 2.05-1.33-1.43s-0.0486-0.0591-0.1-0.0603a0.131 0.131 0 0 0-0.123 0.0792c-0.02 0.048-0.00568 0.0818 0.0383 0.145l1.14 1.37" fill="#fff" />
+  </g>
+</svg>
diff --git a/scipost_django/templates/bi/scipost.html b/scipost_django/templates/bi/scipost.html
new file mode 100644
index 0000000000000000000000000000000000000000..3f2cbfbe9d40a3f831804dc3b0f835ae9231292a
--- /dev/null
+++ b/scipost_django/templates/bi/scipost.html
@@ -0,0 +1,12 @@
+<svg width="8.47mm"
+     height="8.47mm"
+     version="1.1"
+     viewBox="0 0 8.47 8.47"
+     xmlns="http://www.w3.org/2000/svg">
+  <circle cx="4.23" cy="4.23" r="4.23" fill="#002b4b" stop-color="#000000" style="-inkscape-stroke:none" />
+  <g transform="matrix(.127 0 0 .127 .759 .563)">
+  <path d="m21.4 15.8v5.16c-2.23-1.57-5.03-2.49-7.43-2.49-2.32 0-3.71 1.14-3.71 2.97 0 5.55 12.1 5.55 12.1 15.2 0 4.72-3.67 7.73-9.31 7.73-3.23 0-6.12-0.787-7.73-2.14v-5.2c1.84 1.62 4.41 2.58 6.86 2.58 2.67 0 4.19-1.09 4.19-3.02 0-6.6-12.1-6.12-12.1-15.2 0-4.68 3.67-7.69 9.44-7.69 2.97 0 5.86 0.787 7.73 2.1" fill="#cbe0f5" />
+  <path d="m26 6.97v44h3.65v-44h-3.65" fill="#f68b17" />
+  <path d="m42.9 30.4c3.45 0 5.42-2.1 5.42-5.72 0-3.5-1.97-5.59-5.42-5.59h-3.36v11.3zm-0.393-16.2c7.43 0 11.7 3.8 11.7 10.4s-4.24 10.4-11.6 10.4h-3.06v8.74h-5.72v-29.5h8.7" fill="#647ec8" />
+  </g>
+</svg>