diff --git a/.buildpacks b/.buildpacks
deleted file mode 100644
index 5e3076321eec9fec4b5259e0995b621324308cfc..0000000000000000000000000000000000000000
--- a/.buildpacks
+++ /dev/null
@@ -1,2 +0,0 @@
-https://github.com/heroku/heroku-buildpack-nodejs.git#v236
-https://github.com/dokku/heroku-buildpack-nginx.git#v25
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3e1fe65d024604be430bd9dc2ab8781ce4253cf2..c6a70954d1e4f8fa913c0aa02b200ca2918307db 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -10,6 +10,14 @@ variables:
   CI_REGISTRY: gitlab.fi.muni.cz:5050/inject/container-registry/$CI_REGISTRY_IMAGE
   IMAGE_TAG: $CI_REGISTRY:$VER
   IMAGE_LATEST: $CI_REGISTRY:latest
+  # using "docker" as the host is only possible if you alias the service below
+  DOCKER_HOST: tcp://docker:2375 
+  # could be wrong here but although Docker defaults to overlay2, 
+  # Docker-in-Docker (DIND) does not according to the following GitLab doc: 
+  # https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-the-overlayfs-driver
+  DOCKER_DRIVER: overlay2
+  DOCKER_TLS_CERTDIR: ""
+
 
 default:
   tags:
@@ -45,7 +53,9 @@ tsc-test-job:
 create-image:
   image: docker:20.10.16
   services:
-    - docker:20.10.16-dind
+    - name: docker:20.10.16-dind
+      alias: docker
+      command: ["--tls=false"]
   stage: buildimage
   only:
     refs:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4380c82faf2a9f6c135e48f8cd807b2fa940d732..c89ad7085f932e0e5df2258d92d0c46dcd65d5f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,7 @@
-2024-05-09 - add role and exercise info into team selectors
+2024-05-15 - add role and exercise info into team selectors
+2024-05-15 - fix draft persistance
+2024-05-15 - add a title to the toolbar and improve the section titles
+2024-05-14 - improve new email highlighting
 2024-05-07 - add the option to add not team-visible addresses to the recipients list
 2024-05-07 - add exercise name, rework exercise panel add dialogs
 2024-05-07 - add learning objectives page to the instructor view
diff --git a/frontend/src/analyst/HealthCheck/index.tsx b/frontend/src/analyst/HealthCheck/index.tsx
index 8d69a22c0fd072a0f57ae6ba2fe0aacd1b1c97dd..c81f5dabfe72d1b5f4c9b79072ed828c3e0ebde4 100644
--- a/frontend/src/analyst/HealthCheck/index.tsx
+++ b/frontend/src/analyst/HealthCheck/index.tsx
@@ -1,14 +1,44 @@
 import type { SpinnerProps } from '@blueprintjs/core'
 import { Spinner } from '@blueprintjs/core'
 import { useApolloNetworkStatus } from '@inject/graphql/client'
+import useApolloClient from '@inject/graphql/client/useApolloClient'
+import { GetExerciseDocument } from '@inject/graphql/queries/GetExercise.generated'
+import { GetExerciseLoopStatusDocument } from '@inject/graphql/queries/GetExerciseLoopStatus.generated'
 import { useGetExerciseTimeLeft } from '@inject/graphql/queries/GetExerciseTimeLeft.generated'
+import { useEffect, useState } from 'react'
 import { HEALTH_CHECK_INTERVAL } from '../utilities'
 
 const HealthCheck = () => {
+  const [wasError, setWasError] = useState(false)
   const { loading, error } = useGetExerciseTimeLeft({
     pollInterval: HEALTH_CHECK_INTERVAL,
   })
   const { numPendingQueries, numPendingMutations } = useApolloNetworkStatus()
+  const client = useApolloClient()
+
+  /**
+   * During a backend server outage, the exercise loop of a running exercise
+   * will be stopped, but the exercise will still be marked as running
+   *
+   * This  inconsistency is resolved in
+   * /frontend/graphql/utils/useExerciseSubscriptionStatus.ts by restarting
+   * the exercise and refetching the exercise loop status
+   *
+   * To be able to detect this inconsistency, the relevant queries need to be
+   * refetched after the backend server outage
+   */
+  useEffect(() => {
+    if (error) {
+      setWasError(true)
+    } else {
+      if (wasError) {
+        setWasError(false)
+        client.refetchQueries({
+          include: [GetExerciseLoopStatusDocument, GetExerciseDocument],
+        })
+      }
+    }
+  }, [client, error, wasError])
 
   let spinnerProps: SpinnerProps
   if (loading) {
diff --git a/frontend/src/components/Sidebar/index.tsx b/frontend/src/components/Sidebar/index.tsx
index 99d1c6774b7042bf2eed158edcfc48e7084f59a9..60f2f55bc7e490745a9b3af39484fd216eaf69e5 100644
--- a/frontend/src/components/Sidebar/index.tsx
+++ b/frontend/src/components/Sidebar/index.tsx
@@ -25,6 +25,12 @@ const sectionName = css`
   font-size: 1rem;
 `
 
+const sidebarTitle = css`
+  margin: 0 0 0.5rem 0.5rem;
+  font-size: 1.2rem;
+  text-align: center;
+`
+
 export interface Section {
   name?: string
   node: ReactNode
@@ -36,6 +42,7 @@ interface SidebarProps {
   width?: number
   sections: Section[]
   showLogo?: boolean
+  title?: string
 }
 
 const Sidebar: FC<SidebarProps> = ({
@@ -44,11 +51,19 @@ const Sidebar: FC<SidebarProps> = ({
   showLogo,
   hideNames,
   width,
+  title,
 }) => (
   <div className={container(position, width)}>
     {position === 'right' && <Divider />}
 
     <div className={sidebar}>
+      {title && (
+        <div>
+          <h2 className={sidebarTitle}>{title}</h2>
+          <Divider />
+        </div>
+      )}
+
       {showLogo && (
         <>
           <InjectLogo
diff --git a/frontend/src/email/EmailForm/InstructorEmailForm.tsx b/frontend/src/email/EmailForm/InstructorEmailForm.tsx
index 7d7626dad907fec8fc6245605923ae71837928c4..f46e2cf8e8687d9d09153ce4b09e5a5f52334e10 100644
--- a/frontend/src/email/EmailForm/InstructorEmailForm.tsx
+++ b/frontend/src/email/EmailForm/InstructorEmailForm.tsx
@@ -81,7 +81,10 @@ const InstructorEmailForm: FC<InstructorEmailFormProps> = ({
             deactivateMilestone,
           },
         },
-        onCompleted: () => notify('Email sent successfully'),
+        onCompleted: () => {
+          discardDraft()
+          notify('Email sent successfully')
+        },
       })
       onFinish()
     },
@@ -205,9 +208,9 @@ const InstructorEmailForm: FC<InstructorEmailFormProps> = ({
               type='submit'
               rightIcon='send-message'
               onClick={onSubmit}
-              disabled={loading}
+              loading={loading}
             >
-              {loading ? 'Sending...' : 'Send'}
+              Send
             </Button>
           </ButtonGroup>
         </FormGroup>
diff --git a/frontend/src/email/EmailForm/TraineeEmailForm.tsx b/frontend/src/email/EmailForm/TraineeEmailForm.tsx
index 757f4ccf6d7da01c0ab9f3ca3034cc7bbba10f5e..46f278719fb9779ca646130d652da71e7c1a2151 100644
--- a/frontend/src/email/EmailForm/TraineeEmailForm.tsx
+++ b/frontend/src/email/EmailForm/TraineeEmailForm.tsx
@@ -70,7 +70,10 @@ const TraineeEmailForm: FC<TraineeEmailFormProps> = ({
             fileId: fileInfo?.id,
           },
         },
-        onCompleted: () => notify('Email sent successfully'),
+        onCompleted: () => {
+          discardDraft()
+          notify('Email sent successfully')
+        },
       })
       onFinish()
     },
@@ -137,9 +140,9 @@ const TraineeEmailForm: FC<TraineeEmailFormProps> = ({
               type='submit'
               rightIcon='send-message'
               onClick={onSend}
-              disabled={loading}
+              loading={loading}
             >
-              {loading ? 'Sending...' : 'Send'}
+              Send
             </Button>
           </ButtonGroup>
         </FormGroup>
diff --git a/frontend/src/email/TeamEmails/EmailCard.tsx b/frontend/src/email/TeamEmails/EmailCard.tsx
index bd5c78d5541c98c454faa94817d06eb08cc4c839..8fe1ca6eefb3fce4a8b95bfbcadda2684021859d 100644
--- a/frontend/src/email/TeamEmails/EmailCard.tsx
+++ b/frontend/src/email/TeamEmails/EmailCard.tsx
@@ -1,12 +1,12 @@
 import useFormatTimestamp from '@/analyst/useFormatTimestamp'
 import { HIGHLIGHTED_COLOR } from '@/analyst/utilities'
 import FileViewRedirectButton from '@/components/FileViewRedirectButton'
-import { Classes, Icon, Section, SectionCard } from '@blueprintjs/core'
+import { Classes, Colors, Icon, Section, SectionCard } from '@blueprintjs/core'
 import type { Email } from '@inject/graphql/fragments/Email.generated'
 import { useWriteReadReceiptEmail } from '@inject/graphql/mutations/clientonly/WriteReadReceiptEmail.generated'
 import Timestamp from '@inject/shared/components/Timestamp'
 import type { FC } from 'react'
-import { useEffect, useState } from 'react'
+import { useEffect, useMemo } from 'react'
 
 interface EmailCardProps {
   exerciseId: string
@@ -25,21 +25,22 @@ const EmailCard: FC<EmailCardProps> = ({
 }) => {
   const formatTimestamp = useFormatTimestamp()
 
-  const [newIndicator, setNewIndicator] = useState(false)
+  // this ensures the message is rendered as 'not read' the first time it's rendered
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+  const initialReadReceipt = useMemo(() => email.readReceipt, [])
   const [mutate] = useWriteReadReceiptEmail({
     variables: { emailId: email.id },
   })
   useEffect(() => {
     if (!email.readReceipt) {
       mutate()
-      setNewIndicator(() => true)
     }
   }, [email.readReceipt, mutate])
 
   return (
     <Section
       style={{
-        background: newIndicator ? HIGHLIGHTED_COLOR : undefined,
+        background: initialReadReceipt ? undefined : HIGHLIGHTED_COLOR,
         overflow: 'unset',
       }}
       icon={
@@ -63,7 +64,16 @@ const EmailCard: FC<EmailCardProps> = ({
           {inAnalyst ? (
             formatTimestamp(email.timestamp)
           ) : (
-            <Timestamp minimal datetime={new Date(email.timestamp)} />
+            <Timestamp
+              minimal
+              datetime={new Date(email.timestamp)}
+              style={{
+                backgroundColor: initialReadReceipt
+                  ? `${Colors.GREEN3}4d`
+                  : `${Colors.ORANGE3}4d`,
+                alignSelf: 'center',
+              }}
+            />
           )}
         </span>
       }
diff --git a/frontend/src/email/TeamEmails/ThreadLogCard.tsx b/frontend/src/email/TeamEmails/ThreadLogCard.tsx
index d52fa5283a947cf711ab88cf5a1f3bd96e2155df..fae9976009a1b759e2b13bccca0ff6b01bdf0867 100644
--- a/frontend/src/email/TeamEmails/ThreadLogCard.tsx
+++ b/frontend/src/email/TeamEmails/ThreadLogCard.tsx
@@ -1,6 +1,6 @@
 import useFormatTimestamp from '@/analyst/useFormatTimestamp'
 import { HIGHLIGHTED_COLOR, ellipsized } from '@/analyst/utilities'
-import { Card, Classes, Icon } from '@blueprintjs/core'
+import { Card, Classes, Colors, Icon } from '@blueprintjs/core'
 import type { Email } from '@inject/graphql/fragments/Email.generated'
 import type { EmailThread } from '@inject/graphql/fragments/EmailThread.generated'
 import { useWriteReadReceiptEmailThread } from '@inject/graphql/mutations/clientonly/WriteReadReceiptEmailThread.generated'
@@ -94,7 +94,16 @@ const ThreadLogCard: FC<ThreadLogCardProps> = ({
           {inAnalyst ? (
             formatTimestamp(lastEmail.timestamp)
           ) : (
-            <Timestamp minimal datetime={new Date(lastEmail.timestamp)} />
+            <Timestamp
+              minimal
+              datetime={new Date(lastEmail.timestamp)}
+              style={{
+                backgroundColor: emailThread.readReceipt
+                  ? `${Colors.GREEN3}4d`
+                  : `${Colors.ORANGE3}4d`,
+                alignSelf: 'center',
+              }}
+            />
           )}
         </span>
         <Icon icon='chevron-right' className={Classes.TEXT_MUTED} />
diff --git a/frontend/src/views/TraineeView/Toolbar.tsx b/frontend/src/views/TraineeView/Toolbar.tsx
index 5f792fad8f531363447e38448f28d3f3e887b2c0..76c406ba6ccee93715e9fd059af49789ca21099f 100644
--- a/frontend/src/views/TraineeView/Toolbar.tsx
+++ b/frontend/src/views/TraineeView/Toolbar.tsx
@@ -23,24 +23,28 @@ const Toolbar: FC<ToolbarProps> = ({ teamId, exerciseId }) => {
   })
   const process = (data?.teamTools ?? []).filter(notEmpty)
 
-  const groups = useMemo(() => {
-    const map = new Map<string, Tool[]>()
+  const groups: Map<string | undefined, Tool[]> = useMemo(() => {
+    const uncategorized: Tool[] = []
 
-    process.forEach(x => {
-      let prefix = x.name.split('_')[0]!
+    const groupsMap = new Map<string | undefined, Tool[]>()
+    process.forEach(tool => {
+      const prefix = tool.name.split('_').at(0)
 
-      if (prefix === x.name) {
-        prefix = 'Uncategorized'
+      if (!prefix || prefix === tool.name) {
+        uncategorized.push(tool)
+        return
       }
 
-      if (map.has(prefix)) {
-        map.get(prefix)?.push(x)
+      if (groupsMap.has(prefix)) {
+        groupsMap.get(prefix)?.push(tool)
       } else {
-        map.set(prefix, [x])
+        groupsMap.set(prefix, [tool])
       }
     })
 
-    return map
+    groupsMap.set(groupsMap.size === 0 ? undefined : 'Other', uncategorized)
+
+    return groupsMap
   }, [process])
 
   const sections: Section[] = [...groups.entries()].map(([key, value]) => ({
@@ -58,7 +62,14 @@ const Toolbar: FC<ToolbarProps> = ({ teamId, exerciseId }) => {
     ),
   }))
 
-  return <Sidebar position='right' sections={sections} width={300} />
+  return (
+    <Sidebar
+      position='right'
+      sections={sections}
+      width={300}
+      title='Available tools'
+    />
+  )
 }
 
 export default memo(Toolbar)
diff --git a/graphql/utils/useExerciseSubscriptionStatus.ts b/graphql/utils/useExerciseSubscriptionStatus.ts
index 293285af81c05fb894edd6569fab07c7e1b576ea..3ba409bfebf1df49a7dd10e993a321a061a6e37a 100644
--- a/graphql/utils/useExerciseSubscriptionStatus.ts
+++ b/graphql/utils/useExerciseSubscriptionStatus.ts
@@ -1,10 +1,17 @@
 import { useEffect } from 'react'
+import { useStartExercise } from '../mutations/StartExercise.generated'
+import { useStopExercise } from '../mutations/StopExercise.generated'
+import { useGetExercise } from '../queries/GetExercise.generated'
 import { useGetExerciseLoopStatus } from '../queries/GetExerciseLoopStatus.generated'
 import type { ExerciseLoopRunningSubscriptionResult } from '../subscriptions/ExerciseLoopStatus.generated'
 import { ExerciseLoopRunningDocument } from '../subscriptions/ExerciseLoopStatus.generated'
 
 const useExerciseSubscriptionStatus = (exerciseId?: string) => {
-  const { data: dataExerciseLoop, subscribeToMore } = useGetExerciseLoopStatus({
+  const {
+    data: dataExerciseLoop,
+    subscribeToMore,
+    refetch: refetchExerciseLoop,
+  } = useGetExerciseLoopStatus({
     fetchPolicy: 'network-only',
     nextFetchPolicy: 'cache-first',
     variables: {
@@ -12,6 +19,13 @@ const useExerciseSubscriptionStatus = (exerciseId?: string) => {
     },
     skip: !exerciseId,
   })
+  const { data: dataExercise } = useGetExercise({
+    variables: { exerciseId: exerciseId || '' },
+    skip: !exerciseId,
+  })
+  const [stopExercise] = useStopExercise()
+  const [startExercise] = useStartExercise()
+
   useEffect(() => {
     if (exerciseId) {
       return subscribeToMore({
@@ -29,6 +43,52 @@ const useExerciseSubscriptionStatus = (exerciseId?: string) => {
       })
     }
   }, [subscribeToMore, exerciseId])
+
+  /**
+   * During a backend server outage, the exercise loop of a running exercise
+   * will be stopped, but the exercise will still be marked as running
+   *
+   * This useEffect resolves this inconsistency by restarting the exercise and
+   * refetching the exercise loop status; only one client can do this at a time
+   *
+   * Note that the issue will persist if the exercise is not subscribed to at
+   * the moment of the backend server failure
+   */
+  useEffect(() => {
+    const asyncEffect = async () => {
+      while (
+        dataExerciseLoop?.exerciseLoopRunning === false &&
+        dataExercise?.exerciseId?.running === true &&
+        exerciseId
+      ) {
+        const lock = localStorage.getItem('backendOutageFixLock')
+        if (lock) {
+          await new Promise(resolve => setTimeout(resolve, 1000))
+          await refetchExerciseLoop()
+          return
+        }
+
+        localStorage.setItem('backendOutageFixLock', 'true')
+        stopExercise({
+          variables: { id: exerciseId },
+          onCompleted: () => {
+            startExercise({
+              variables: { id: exerciseId },
+              onCompleted: () => {
+                localStorage.removeItem('backendOutageFixLock')
+                refetchExerciseLoop()
+              },
+            })
+          },
+        })
+        break
+      }
+    }
+
+    asyncEffect()
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [dataExercise?.exerciseId?.running, dataExerciseLoop?.exerciseLoopRunning])
+
   return dataExerciseLoop?.exerciseLoopRunning
 }