diff --git a/frontend/src/editor/CommitDefinition/ProjectPanel.tsx b/frontend/src/editor/CommitDefinition/ProjectPanel.tsx
index 49aba361fb2ba36d4850999451bc3e5be500351c..da0a9ab7409304212e77b9fde63ca8c12be221f1 100644
--- a/frontend/src/editor/CommitDefinition/ProjectPanel.tsx
+++ b/frontend/src/editor/CommitDefinition/ProjectPanel.tsx
@@ -4,6 +4,10 @@ import type { FC } from 'react'
 import { memo } from 'react'
 import TooltipCheckbox from '../Tooltips/TooltipCheckbox'
 import TooltipLabel from '../Tooltips/TooltipLabel'
+import {
+  COMMIT_PROJECT_PANEL_CONTENT_1,
+  COMMIT_PROJECT_PANEL_CONTENT_2,
+} from '../assets/dialogContent'
 import { COMMIT_FORM } from '../assets/pageContent/gitlab'
 import useGitlabStorage from '../useGitlabStorage'
 
@@ -48,9 +52,8 @@ const ProjectPanel: FC<ProjectPanelProps> = ({
       )}
       {!newProject && (
         <p>
-          The definition changes will be commited to the{' '}
-          <b>{gitlabConfig?.project?.name}</b> project. To create a new project,
-          click on the new project option and fill in the necessary information.
+          {COMMIT_PROJECT_PANEL_CONTENT_1} <b>{gitlabConfig?.project?.name}</b>{' '}
+          {COMMIT_PROJECT_PANEL_CONTENT_2}
         </p>
       )}
       {(!gitlabConfig?.project || newProject) && (
diff --git a/frontend/src/editor/CommitDefinition/index.tsx b/frontend/src/editor/CommitDefinition/index.tsx
index 8fd5a2b2aac918e53d222769a8915ad0b220c5fd..7016e305199de567d72ce93d73737631a787ae10 100644
--- a/frontend/src/editor/CommitDefinition/index.tsx
+++ b/frontend/src/editor/CommitDefinition/index.tsx
@@ -3,6 +3,7 @@ import { Button, ButtonGroup, Dialog, DialogFooter } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { memo, useCallback, useEffect, useMemo, useState } from 'react'
 import useBranches from '../BranchSelector/useBranches'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import {
   commitDefinitionFiles,
   createBranch,
@@ -141,7 +142,8 @@ const CommitDefinition = () => {
   ])
 
   const nextButtonText = useMemo(
-    () => (currentStep === STEPS.COMMIT ? 'Commit' : 'Next'),
+    () =>
+      currentStep === STEPS.COMMIT ? 'Commit' : GENERIC_CONTENT.buttons.next,
     [currentStep]
   )
   const projectUnfilled = useMemo(
@@ -231,7 +233,7 @@ const CommitDefinition = () => {
                 <Button
                   onClick={() => prevStep()}
                   icon='arrow-left'
-                  text='Back'
+                  text={GENERIC_CONTENT.buttons.back}
                 />
               )}
               <Button
diff --git a/frontend/src/editor/DataRemovalDialog/index.tsx b/frontend/src/editor/DataRemovalDialog/index.tsx
index 7d579872160e7361429d95920380ff616667d998..949ae8d3680f178cb39d7ea67d332d3607229104 100644
--- a/frontend/src/editor/DataRemovalDialog/index.tsx
+++ b/frontend/src/editor/DataRemovalDialog/index.tsx
@@ -9,6 +9,8 @@ import {
   DialogFooter,
 } from '@blueprintjs/core'
 import { memo, useCallback, useState, type FC } from 'react'
+import { DATA_REMOVAL_CONTENT } from '../assets/dialogContent'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { clearDb } from '../indexeddb/operations'
 import useEditorAccessStorage from '../useEditorAccessStorage'
 import useEditorStorage from '../useEditorStorage'
@@ -49,10 +51,7 @@ const DataRemovalDialog: FC<DataRemovalDialogProps> = ({
         title='Clear definition'
       >
         <DialogBody>
-          <p>
-            This will permanently delete all data in the editor. Are you sure
-            you want to clear the definition?
-          </p>
+          <p>{DATA_REMOVAL_CONTENT}</p>
         </DialogBody>
         <DialogFooter
           actions={
@@ -60,7 +59,7 @@ const DataRemovalDialog: FC<DataRemovalDialogProps> = ({
               <Button
                 onClick={() => setIsOpen(false)}
                 icon='cross'
-                text='Cancel'
+                text={GENERIC_CONTENT.buttons.cancel}
               />
               <Button
                 onClick={() => handleConfirmButton()}
diff --git a/frontend/src/editor/DefinitionImportDialog/index.tsx b/frontend/src/editor/DefinitionImportDialog/index.tsx
index a00be62ad63d3e38eefd7a62645b2f677708c33c..6708eb61eb608b659f2cfaa146d262a22f29ac5b 100644
--- a/frontend/src/editor/DefinitionImportDialog/index.tsx
+++ b/frontend/src/editor/DefinitionImportDialog/index.tsx
@@ -7,9 +7,16 @@ import {
   DialogBody,
   DialogFooter,
 } from '@blueprintjs/core'
+import { useAuthIdentity } from '@inject/graphql/auth'
+import { useHost } from '@inject/graphql/connection/host'
+import { validateDefinitionUrl } from '@inject/shared/config'
+import { notify } from '@inject/shared/notification/engine'
+import csrfFetch from '@inject/shared/utils/csrfFetch'
 import JSZip from 'jszip'
 import type { FC, ReactNode } from 'react'
 import { memo, useCallback, useState } from 'react'
+import { DEFINITION_IMPORT_CONTENT } from '../assets/dialogContent'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { clearDb } from '../indexeddb/operations'
 import useEditorStorage from '../useEditorStorage'
 import type { GitlabConfig } from '../useGitlabStorage'
@@ -31,11 +38,32 @@ const DefinitionImportDialog: FC<DefinitionImportDialogProps> = ({
   const [isOpen, setIsOpen] = useState(false)
   const [, setConfig] = useEditorStorage()
   const nav = useNavigate()
+  const host = useHost()
+  const { isStaff } = useAuthIdentity()
 
   const handleAdd = useCallback(async () => {
     const result = await onAdd()
     if (!result || !result.file) return
 
+    const data = new FormData()
+    data.append('file', result.file)
+
+    if (isStaff) {
+      csrfFetch(validateDefinitionUrl(host || ''), {
+        method: 'POST',
+        body: data,
+        credentials: 'include',
+      })
+        .then(res => res.json())
+        .then((res: { status: string; detail: string }) => {
+          if (res.status === 'error') {
+            notify(res.detail, { intent: 'danger' })
+            return
+          }
+          notify(res.detail, { intent: 'success' })
+        })
+    }
+
     await clearDb()
 
     const zip = await new JSZip().loadAsync(result.file)
@@ -57,10 +85,7 @@ const DefinitionImportDialog: FC<DefinitionImportDialogProps> = ({
         title='Upload definition'
       >
         <DialogBody>
-          <p>
-            This will permanently delete all data in the editor. Are you sure
-            you want to upload new definition?
-          </p>
+          <p>{DEFINITION_IMPORT_CONTENT}</p>
           {children}
         </DialogBody>
         <DialogFooter
@@ -69,14 +94,14 @@ const DefinitionImportDialog: FC<DefinitionImportDialogProps> = ({
               <Button
                 onClick={() => setIsOpen(false)}
                 icon='cross'
-                text='Cancel'
+                text={GENERIC_CONTENT.buttons.cancel}
               />
               <Button
                 disabled={addDisabled}
                 onClick={() => handleAdd()}
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             </ButtonGroup>
           }
diff --git a/frontend/src/editor/DownloadDefinition/index.tsx b/frontend/src/editor/DownloadDefinition/index.tsx
index b8af1c7537063d823e1159283a5f5d266c9e49fc..ef822f3d0373f7e8ee40697487190f911f061474 100644
--- a/frontend/src/editor/DownloadDefinition/index.tsx
+++ b/frontend/src/editor/DownloadDefinition/index.tsx
@@ -4,6 +4,7 @@ import { Button, ButtonGroup } from '@blueprintjs/core'
 import { memo } from 'react'
 import CommitDefinition from '../CommitDefinition'
 import DataRemovalDialog from '../DataRemovalDialog'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 
 const DownloadDefinition = () => {
   const [config] = useEditorStorage()
@@ -26,7 +27,7 @@ const DownloadDefinition = () => {
         confirmButtonProps={{
           intent: 'primary',
           icon: 'tick',
-          text: 'Confirm',
+          text: GENERIC_CONTENT.buttons.confirm,
         }}
         redirectTo='/editor'
       />
diff --git a/frontend/src/editor/EditorPage/index.tsx b/frontend/src/editor/EditorPage/index.tsx
index deaaab7751053a59abb9631c771379a7548dfe7e..ade0fbf1631fb2f4692b2015aed73b5535c6d39d 100644
--- a/frontend/src/editor/EditorPage/index.tsx
+++ b/frontend/src/editor/EditorPage/index.tsx
@@ -1,10 +1,11 @@
 import type { Path } from '@/router'
 import { useNavigate } from '@/router'
-import { Button } from '@blueprintjs/core'
+import { Button, NonIdealState } from '@blueprintjs/core'
 import { css } from '@emotion/css'
 import type { FC, ReactNode } from 'react'
 import { memo } from 'react'
 import FaqSection from '../FaqSection'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { PAGE_INFORMATION } from '../assets/pageInformation'
 import type { PageNames } from '../types'
 
@@ -46,7 +47,7 @@ const EditorPage: FC<EditorPageProps> = ({
   const content = PAGE_INFORMATION[pageKey]
 
   if (!pageVisible) {
-    nav('/editor')
+    return <NonIdealState title='Page not found' icon='warning-sign' />
   }
 
   return (
@@ -77,7 +78,7 @@ const EditorPage: FC<EditorPageProps> = ({
         <Button
           type='button'
           onClick={() => nav(prevPath)}
-          text='Back'
+          text={GENERIC_CONTENT.buttons.back}
           icon='arrow-left'
           style={{
             marginRight: '0.5rem',
@@ -87,7 +88,7 @@ const EditorPage: FC<EditorPageProps> = ({
           <Button
             type='button'
             onClick={() => nav(nextPath || '/')}
-            text='Next'
+            text={GENERIC_CONTENT.buttons.next}
             intent='primary'
             rightIcon='arrow-right'
             disabled={nextDisabled}
diff --git a/frontend/src/editor/EmailAddressForm/index.tsx b/frontend/src/editor/EmailAddressForm/index.tsx
index 72f1692918485a88db3b669e015473478920d8ba..817e27371e96d1463333d683222b586e14f22abf 100644
--- a/frontend/src/editor/EmailAddressForm/index.tsx
+++ b/frontend/src/editor/EmailAddressForm/index.tsx
@@ -15,6 +15,7 @@ import type { FC } from 'react'
 import { memo, useCallback, useEffect, useState } from 'react'
 import TooltipCheckbox from '../Tooltips/TooltipCheckbox'
 import TooltipLabel from '../Tooltips/TooltipLabel'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { EMAIL_ADDRESS_FORM } from '../assets/pageContent/emails'
 import type { EmailAddressInfo } from '../indexeddb/types'
 
@@ -140,7 +141,7 @@ const EmailAddressForm: FC<EmailAddressFormProps> = ({
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -155,7 +156,7 @@ const EmailAddressForm: FC<EmailAddressFormProps> = ({
                 }
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/EmailAddresses/EmailAddress.tsx b/frontend/src/editor/EmailAddresses/EmailAddress.tsx
index e3e95d670478af2fccc5342e52757d2646d48c96..e5f8a08135ad37cb7932b7049c7d8e2ef9308b5a 100644
--- a/frontend/src/editor/EmailAddresses/EmailAddress.tsx
+++ b/frontend/src/editor/EmailAddresses/EmailAddress.tsx
@@ -1,4 +1,5 @@
 import { Button, ButtonGroup, Card } from '@blueprintjs/core'
+import { css } from '@emotion/css'
 import { notify } from '@inject/shared/notification/engine'
 import type { FC } from 'react'
 import { memo, useCallback } from 'react'
@@ -8,6 +9,24 @@ import EmailTemplates from '../EmailTemplates'
 import { deleteEmailAddress } from '../indexeddb/operations'
 import type { EmailAddressInfo } from '../indexeddb/types'
 
+const emailCard = css`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding: 0rem 1rem 1rem 0;
+`
+
+const emailCardName = css`
+  height: 100%;
+  flex-grow: 1;
+`
+
+const emailTemplates = css`
+  width: 100%;
+  padding-left: 2rem;
+`
+
 interface EmailAddressProps {
   emailAddress: EmailAddressInfo
 }
@@ -31,18 +50,8 @@ const EmailAddressItem: FC<EmailAddressProps> = ({ emailAddress }) => {
 
   return (
     <Card style={{ flexDirection: 'column' }}>
-      <div
-        style={{
-          display: 'flex',
-          justifyContent: 'space-between',
-          alignItems: 'center',
-          width: '100%',
-          padding: '0rem 1rem 1rem 0',
-        }}
-      >
-        <span style={{ height: '100%', flexGrow: 1 }}>
-          {emailAddress.address}
-        </span>
+      <div className={emailCard}>
+        <span className={emailCardName}>{emailAddress.address}</span>
         <ButtonGroup>
           <EmailAddressForm
             emailAddressInfo={emailAddress}
@@ -59,7 +68,7 @@ const EmailAddressItem: FC<EmailAddressProps> = ({ emailAddress }) => {
           />
         </ButtonGroup>
       </div>
-      <div style={{ width: '100%', paddingLeft: '2rem' }}>
+      <div className={emailTemplates}>
         <EmailTemplates emailAddressId={Number(emailAddress.id)} />
         <EmailTemplateFormDialog
           emailAddressId={Number(emailAddress.id)}
diff --git a/frontend/src/editor/EmailTemplateFormDialog/index.tsx b/frontend/src/editor/EmailTemplateFormDialog/index.tsx
index 1f06651537ef1a5818a839c1c016017340f2a033..cd1b0a3b331fd17c98fb46a7800f0948000d8416 100644
--- a/frontend/src/editor/EmailTemplateFormDialog/index.tsx
+++ b/frontend/src/editor/EmailTemplateFormDialog/index.tsx
@@ -3,6 +3,7 @@ import { Button, Dialog, DialogBody, DialogFooter } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
 import EmailTemplateFormContent from '../EmailTemplateFormContent'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { addEmailTemplate, updateEmailTemplate } from '../indexeddb/operations'
 import type { EmailTemplate } from '../indexeddb/types'
 
@@ -106,7 +107,7 @@ const EmailTemplateFormDialog: FC<EmailTemplateFormDialogProps> = ({
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -121,7 +122,7 @@ const EmailTemplateFormDialog: FC<EmailTemplateFormDialogProps> = ({
                 }
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/ExpressionBuilder/ExpressionBlock.tsx b/frontend/src/editor/ExpressionBuilder/ExpressionBlock.tsx
index 9667555b93d99e902d888ddf9e48551826bb34fe..736deb0b33254e2f314366ca0a52cac0f0acbc8d 100644
--- a/frontend/src/editor/ExpressionBuilder/ExpressionBlock.tsx
+++ b/frontend/src/editor/ExpressionBuilder/ExpressionBlock.tsx
@@ -48,7 +48,7 @@ const ExpressionBlock: FC<ExpressionBlockProps> = ({
         onChange={event => {
           const selectedOption = event.currentTarget.selectedOptions[0]
           onModify({
-            value: selectedOption.value,
+            value: Number(selectedOption.value),
             label: selectedOption.label,
           })
         }}
diff --git a/frontend/src/editor/ExpressionBuilder/index.tsx b/frontend/src/editor/ExpressionBuilder/index.tsx
index 7d82dd9eab1510b9983ea2caaf48a4dd3c4b1fe6..6fd3c06bae18649332e3ffff21640dff06c86076 100644
--- a/frontend/src/editor/ExpressionBuilder/index.tsx
+++ b/frontend/src/editor/ExpressionBuilder/index.tsx
@@ -186,7 +186,7 @@ const ExpressionBuilder: FC<ExpressionBuilderProps> = ({
                 onChange={event => {
                   const selectedOption = event.currentTarget.selectedOptions[0]
                   addBlock({
-                    value: selectedOption.value,
+                    value: Number(selectedOption.value),
                     label: selectedOption.label,
                   })
                 }}
diff --git a/frontend/src/editor/ExpressionBuilder/useValidateExpression.tsx b/frontend/src/editor/ExpressionBuilder/useValidateExpression.tsx
index 71383eddd6efb64dbd7d85ea7a9e43463e96ba71..fe271e9f8ff67314c791e09d2c044b385bb0229b 100644
--- a/frontend/src/editor/ExpressionBuilder/useValidateExpression.tsx
+++ b/frontend/src/editor/ExpressionBuilder/useValidateExpression.tsx
@@ -1,20 +1,14 @@
 import { useLiveQuery } from 'dexie-react-hooks'
 import { useMemo } from 'react'
-import { getMilestonesWithNames } from '../indexeddb/joins'
-import { getExpression, getVariables, validateExpression } from '../utils'
+import { db } from '../indexeddb/db'
+import { validateExpression } from '../utils'
 
 const useValidateExpression = (expression?: number[]) => {
-  const milestones = useLiveQuery(() => getMilestonesWithNames(), [], [])
-
-  const variables = useMemo(() => getVariables(milestones), [milestones])
-  const newExpression = useMemo(
-    () => getExpression(expression || [], variables),
-    [expression, variables]
-  )
+  const milestones = useLiveQuery(() => db.milestones.toArray(), [], [])
 
   const { isValid, error } = useMemo(
-    () => validateExpression(newExpression, variables),
-    [newExpression, variables]
+    () => validateExpression(expression || [], milestones),
+    [expression, milestones]
   )
 
   return { isValid, error }
diff --git a/frontend/src/editor/FileUploader/index.tsx b/frontend/src/editor/FileUploader/index.tsx
index 93966ab009f4d5190faf0f6fa209b86b40ab65c0..bdcf8f5f9b32b530bbf61cfb36f6183f06a5fb30 100644
--- a/frontend/src/editor/FileUploader/index.tsx
+++ b/frontend/src/editor/FileUploader/index.tsx
@@ -12,6 +12,7 @@ import { notify } from '@inject/shared/notification/engine'
 import type { ChangeEvent, FC } from 'react'
 import { useCallback, useEffect, useMemo, useState } from 'react'
 import TooltipLabel from '../Tooltips/TooltipLabel'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { FILE_UPLOAD_FORM } from '../assets/pageContent/files'
 import { addFile, updateFile } from '../indexeddb/operations'
 import type { ContentFile } from '../indexeddb/types'
@@ -130,7 +131,7 @@ const FileUploader: FC<FileUploaderProps> = ({
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -145,7 +146,7 @@ const FileUploader: FC<FileUploaderProps> = ({
                 }
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/FinalInformationForm/FinalMilestoneForm.tsx b/frontend/src/editor/FinalInformationForm/FinalMilestoneForm.tsx
index 5348f3348fecd5636a1539f3dab78cd0f7cf3ef2..e5fc1e9fa4e6638d2d1a9a4f129212513fe1399b 100644
--- a/frontend/src/editor/FinalInformationForm/FinalMilestoneForm.tsx
+++ b/frontend/src/editor/FinalInformationForm/FinalMilestoneForm.tsx
@@ -1,7 +1,7 @@
 import type { OptionProps } from '@blueprintjs/core'
 import { HTMLSelect } from '@blueprintjs/core'
 import { useLiveQuery } from 'dexie-react-hooks'
-import { memo, useEffect, useMemo, type FC } from 'react'
+import { memo, useMemo, type FC } from 'react'
 import TooltipLabel from '../Tooltips/TooltipLabel'
 import { FINAL_MILESTONE_FORM } from '../assets/pageContent/finalInformation'
 import { getMilestonesWithNames } from '../indexeddb/joins'
@@ -29,15 +29,9 @@ const FinalMilestoneForm: FC<FinalMilestoneFormProps> = ({
       ]
     }
 
-    return getVariables(milestones)
+    return [{ label: 'none', value: 0 }, ...getVariables(milestones)]
   }, [milestones])
 
-  useEffect(() => {
-    if (!finalMilestone && milestones && milestones.length > 0) {
-      onFinalMilestoneChange(milestones[0].id)
-    }
-  }, [finalMilestone, milestones])
-
   return (
     <TooltipLabel label={FINAL_MILESTONE_FORM.finalMilestone}>
       <HTMLSelect
diff --git a/frontend/src/editor/InjectForm/index.tsx b/frontend/src/editor/InjectForm/index.tsx
index 54edeb79fe43e56911f64836c1516be8dc3c0eeb..3b22a57359006cc523bede1c8e37055cd5d4ea39 100644
--- a/frontend/src/editor/InjectForm/index.tsx
+++ b/frontend/src/editor/InjectForm/index.tsx
@@ -12,6 +12,7 @@ import { notify } from '@inject/shared/notification/engine'
 import type { FC } from 'react'
 import { memo, useCallback, useEffect, useState } from 'react'
 import TooltipLabel from '../Tooltips/TooltipLabel'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { INJECT_FORM } from '../assets/pageContent/injects'
 import { addInjectInfo, updateInjectInfo } from '../indexeddb/operations'
 import type { InjectInfo } from '../indexeddb/types'
@@ -124,7 +125,7 @@ const InjectForm: FC<InjectFormProps> = ({ inject, buttonProps }) => {
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -132,7 +133,7 @@ const InjectForm: FC<InjectFormProps> = ({ inject, buttonProps }) => {
                 onClick={() => handleAddButton({ name, description, type })}
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/InjectSpecification/ConnectionsForm.tsx b/frontend/src/editor/InjectSpecification/ConnectionsForm.tsx
index 68ff975434df41496e2fab231cbbb22ba01a28da..961f526b24ddf267e0ff6b36249e925f97d8dc40 100644
--- a/frontend/src/editor/InjectSpecification/ConnectionsForm.tsx
+++ b/frontend/src/editor/InjectSpecification/ConnectionsForm.tsx
@@ -1,9 +1,10 @@
-import { Button, NumericInput } from '@blueprintjs/core'
+import { NumericInput } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
 import ExpressionBuilder from '../ExpressionBuilder'
 import useValidateExpression from '../ExpressionBuilder/useValidateExpression'
+import SaveButtonGroup from '../SaveButtonGroup'
 import TooltipLabel from '../Tooltips/TooltipLabel'
 import { INJECT_CONNECTIONS_FORM } from '../assets/pageContent/injectSpecification'
 import {
@@ -17,11 +18,15 @@ import { InjectType } from '../indexeddb/types'
 interface ConnectionsFormProps {
   injectInfoId: number
   injectType: InjectType
+  changed: boolean
+  onChangedChange: (value: boolean) => void
 }
 
 const ConnectionsForm: FC<ConnectionsFormProps> = ({
   injectInfoId,
   injectType,
+  changed,
+  onChangedChange,
 }) => {
   const injectControl = useLiveQuery(
     () => getInjectControlByInjectInfoId(injectInfoId),
@@ -42,7 +47,7 @@ const ConnectionsForm: FC<ConnectionsFormProps> = ({
     setMilestoneCondition(injectControl?.milestoneCondition || [])
   }, [injectControl, injectType])
 
-  const handleUpdateButton = useCallback(
+  const update = useCallback(
     async (newInjectControl: InjectControl | Omit<InjectControl, 'id'>) => {
       try {
         if (injectControl) {
@@ -62,6 +67,21 @@ const ConnectionsForm: FC<ConnectionsFormProps> = ({
     [notify, injectControl]
   )
 
+  const handleUpdate = useCallback(async () => {
+    if (!changed) onChangedChange(true)
+    await update({
+      injectInfoId,
+      start,
+      delay,
+      milestoneCondition: isValid ? milestoneCondition : [],
+    })
+    onChangedChange(false)
+  }, [changed, injectInfoId, start, delay, milestoneCondition, isValid, update])
+
+  useEffect(() => {
+    if (changed) handleUpdate()
+  }, [changed, handleUpdate])
+
   return (
     <div>
       <TooltipLabel label={INJECT_CONNECTIONS_FORM.time}>
@@ -87,19 +107,10 @@ const ConnectionsForm: FC<ConnectionsFormProps> = ({
         initExpression={injectControl?.milestoneCondition}
         onExpressionChange={expression => setMilestoneCondition(expression)}
       />
-      <Button
-        disabled={!isValid}
-        onClick={() =>
-          handleUpdateButton({
-            injectInfoId,
-            start,
-            delay,
-            milestoneCondition: isValid ? milestoneCondition : [],
-          })
-        }
-        intent='primary'
-        icon='edit'
-        text='Save changes'
+      <SaveButtonGroup
+        isValid={isValid}
+        handleUpdate={() => handleUpdate()}
+        prevPath='/editor/create/inject-specification'
       />
     </div>
   )
diff --git a/frontend/src/editor/InjectSpecification/EmailInjectForm.tsx b/frontend/src/editor/InjectSpecification/EmailInjectForm.tsx
index 239ce31678f73f84a3e9b8e0bbb45f66439c8f8a..b92df38e1b85231d761fbdbe9ea3a42659689cb3 100644
--- a/frontend/src/editor/InjectSpecification/EmailInjectForm.tsx
+++ b/frontend/src/editor/InjectSpecification/EmailInjectForm.tsx
@@ -1,9 +1,10 @@
-import { Button, InputGroup, NumericInput, TextArea } from '@blueprintjs/core'
+import { InputGroup, NumericInput, TextArea } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
 import EmailAddressSelector from '../EmailAddressSelector'
 import FileSelector from '../FileSelector'
+import SaveButtonGroup from '../SaveButtonGroup'
 import TooltipLabel from '../Tooltips/TooltipLabel'
 import { EMAIL_ADDRESS_FORM } from '../assets/pageContent/emails'
 import { EMAIL_INJECT_FORM } from '../assets/pageContent/injectSpecification'
@@ -16,9 +17,15 @@ import type { EmailInject } from '../indexeddb/types'
 
 interface EmailInjectFormProps {
   injectInfoId: number
+  changed: boolean
+  onChangedChange: (value: boolean) => void
 }
 
-const EmailInjectForm: FC<EmailInjectFormProps> = ({ injectInfoId }) => {
+const EmailInjectForm: FC<EmailInjectFormProps> = ({
+  injectInfoId,
+  changed,
+  onChangedChange,
+}) => {
   const emailInject = useLiveQuery(
     () => getEmailInjectByInjectInfoId(injectInfoId),
     [injectInfoId],
@@ -39,7 +46,7 @@ const EmailInjectForm: FC<EmailInjectFormProps> = ({ injectInfoId }) => {
     setFileId(emailInject?.fileId || 0)
   }, [emailInject])
 
-  const handleUpdateButton = useCallback(
+  const updateInject = useCallback(
     async (newEmailInject: EmailInject | Omit<EmailInject, 'id'>) => {
       try {
         if (emailInject) {
@@ -56,6 +63,32 @@ const EmailInjectForm: FC<EmailInjectFormProps> = ({ injectInfoId }) => {
     [notify, emailInject]
   )
 
+  const handleUpdate = useCallback(async () => {
+    if (!changed) onChangedChange(true)
+    await updateInject({
+      injectInfoId,
+      subject,
+      content,
+      emailAddressId: selectedAddressId,
+      extraCopies,
+      fileId,
+    })
+    onChangedChange(false)
+  }, [
+    changed,
+    injectInfoId,
+    subject,
+    content,
+    selectedAddressId,
+    extraCopies,
+    fileId,
+    updateInject,
+  ])
+
+  useEffect(() => {
+    if (changed) handleUpdate()
+  }, [changed, handleUpdate])
+
   return (
     <div>
       <EmailAddressSelector
@@ -96,20 +129,10 @@ const EmailInjectForm: FC<EmailInjectFormProps> = ({ injectInfoId }) => {
         fileId={fileId}
         onChange={id => setFileId(id)}
       />
-      <Button
-        onClick={() =>
-          handleUpdateButton({
-            injectInfoId,
-            subject,
-            content,
-            emailAddressId: selectedAddressId,
-            extraCopies,
-            fileId,
-          })
-        }
-        intent='primary'
-        icon='edit'
-        text='Save changes'
+      <SaveButtonGroup
+        isValid
+        handleUpdate={() => handleUpdate()}
+        prevPath='/editor/create/inject-specification'
       />
     </div>
   )
diff --git a/frontend/src/editor/InjectSpecification/InformationInjectForm.tsx b/frontend/src/editor/InjectSpecification/InformationInjectForm.tsx
index e88a6f33f3b88ef263dfd67a436b16cbb4b9bcfc..d5c4cc85dd4f68966ce2b9dba35866f9d71a6eff 100644
--- a/frontend/src/editor/InjectSpecification/InformationInjectForm.tsx
+++ b/frontend/src/editor/InjectSpecification/InformationInjectForm.tsx
@@ -1,8 +1,9 @@
-import { Button, TextArea } from '@blueprintjs/core'
+import { TextArea } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
 import FileSelector from '../FileSelector'
+import SaveButtonGroup from '../SaveButtonGroup'
 import TooltipLabel from '../Tooltips/TooltipLabel'
 import { INFORMATION_INJECT_FORM } from '../assets/pageContent/injectSpecification'
 import {
@@ -14,10 +15,14 @@ import type { InformationInject } from '../indexeddb/types'
 
 interface InformationInjectFormProps {
   injectInfoId: number
+  changed: boolean
+  onChangedChange: (value: boolean) => void
 }
 
 const InformationInjectForm: FC<InformationInjectFormProps> = ({
   injectInfoId,
+  changed,
+  onChangedChange,
 }) => {
   const informationInject = useLiveQuery(
     () => getInformationInjectByInjectInfoId(injectInfoId),
@@ -33,7 +38,7 @@ const InformationInjectForm: FC<InformationInjectFormProps> = ({
     setFileId(informationInject?.fileId || 0)
   }, [informationInject])
 
-  const handleUpdateButton = useCallback(
+  const updateInject = useCallback(
     async (
       newInformationInject: InformationInject | Omit<InformationInject, 'id'>
     ) => {
@@ -55,6 +60,20 @@ const InformationInjectForm: FC<InformationInjectFormProps> = ({
     [notify, informationInject]
   )
 
+  const handleUpdate = useCallback(async () => {
+    if (!changed) onChangedChange(true)
+    await updateInject({
+      injectInfoId,
+      content,
+      fileId,
+    })
+    onChangedChange(false)
+  }, [changed, injectInfoId, content, fileId, updateInject])
+
+  useEffect(() => {
+    if (changed) handleUpdate()
+  }, [changed, handleUpdate])
+
   return (
     <div>
       <TooltipLabel label={INFORMATION_INJECT_FORM.content}>
@@ -75,17 +94,10 @@ const InformationInjectForm: FC<InformationInjectFormProps> = ({
         fileId={fileId}
         onChange={id => setFileId(id)}
       />
-      <Button
-        onClick={() =>
-          handleUpdateButton({
-            injectInfoId,
-            content,
-            fileId,
-          })
-        }
-        intent='primary'
-        icon='edit'
-        text='Save changes'
+      <SaveButtonGroup
+        isValid
+        handleUpdate={() => handleUpdate()}
+        prevPath='/editor/create/inject-specification'
       />
     </div>
   )
diff --git a/frontend/src/editor/InjectSpecification/OverlayForm.tsx b/frontend/src/editor/InjectSpecification/OverlayForm.tsx
index 9489237f2054df83028505d82f243c23576370eb..1ff87252014da8c657b15deca7d56927b311dfe9 100644
--- a/frontend/src/editor/InjectSpecification/OverlayForm.tsx
+++ b/frontend/src/editor/InjectSpecification/OverlayForm.tsx
@@ -1,7 +1,8 @@
-import { Button, NumericInput } from '@blueprintjs/core'
+import { NumericInput } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
+import SaveButtonGroup from '../SaveButtonGroup'
 import TooltipCheckbox from '../Tooltips/TooltipCheckbox'
 import TooltipLabel from '../Tooltips/TooltipLabel'
 import { OVERLAY_FORM } from '../assets/pageContent/injectSpecification'
@@ -15,9 +16,15 @@ import type { Overlay } from '../indexeddb/types'
 
 interface OverlayFormProps {
   injectInfoId: number
+  changed: boolean
+  onChangedChange: (value: boolean) => void
 }
 
-const OverlayForm: FC<OverlayFormProps> = ({ injectInfoId }) => {
+const OverlayForm: FC<OverlayFormProps> = ({
+  injectInfoId,
+  changed,
+  onChangedChange,
+}) => {
   const overlay = useLiveQuery(
     () => getOverlayByInjectInfoId(injectInfoId),
     [injectInfoId],
@@ -32,7 +39,7 @@ const OverlayForm: FC<OverlayFormProps> = ({ injectInfoId }) => {
     setDuration(overlay?.duration || 1)
   }, [overlay])
 
-  const handleUpdateButton = useCallback(
+  const update = useCallback(
     async (newOverlay: Overlay | Omit<Overlay, 'id'>) => {
       try {
         if (overlay) {
@@ -59,6 +66,19 @@ const OverlayForm: FC<OverlayFormProps> = ({ injectInfoId }) => {
     [notify, overlay, enableOverlay]
   )
 
+  const handleUpdate = useCallback(async () => {
+    if (!changed) onChangedChange(true)
+    await update({
+      injectInfoId,
+      duration,
+    })
+    onChangedChange(false)
+  }, [changed, injectInfoId, duration, update])
+
+  useEffect(() => {
+    if (changed) handleUpdate()
+  }, [changed, handleUpdate])
+
   return (
     <div>
       <TooltipCheckbox
@@ -78,16 +98,10 @@ const OverlayForm: FC<OverlayFormProps> = ({ injectInfoId }) => {
           />
         </TooltipLabel>
       )}
-      <Button
-        onClick={() =>
-          handleUpdateButton({
-            injectInfoId,
-            duration,
-          })
-        }
-        intent='primary'
-        icon='edit'
-        text='Save changes'
+      <SaveButtonGroup
+        isValid
+        handleUpdate={() => handleUpdate()}
+        prevPath='/editor/create/inject-specification'
       />
     </div>
   )
diff --git a/frontend/src/editor/InjectSpecification/QuestionnaireForm/QuestionnaireQuestionForm.tsx b/frontend/src/editor/InjectSpecification/QuestionnaireForm/QuestionnaireQuestionForm.tsx
index 31270ca1fff24af43e6fa9e4139de466cd789f7c..ddb92824efe588f652e8bf1b283e179ffa52c1ea 100644
--- a/frontend/src/editor/InjectSpecification/QuestionnaireForm/QuestionnaireQuestionForm.tsx
+++ b/frontend/src/editor/InjectSpecification/QuestionnaireForm/QuestionnaireQuestionForm.tsx
@@ -1,5 +1,6 @@
 import TooltipLabel from '@/editor/Tooltips/TooltipLabel'
 import TooltipSwitch from '@/editor/Tooltips/TooltipSwitch'
+import { GENERIC_CONTENT } from '@/editor/assets/generalContent'
 import { QUESTIONNAIRE_QUESTION_FORM } from '@/editor/assets/pageContent/injectSpecification'
 import {
   addQuestionnaireQuestion,
@@ -173,7 +174,7 @@ const QuestionnaireQuestionForm: FC<QuestionnaireQuestionFormProps> = ({
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -189,7 +190,7 @@ const QuestionnaireQuestionForm: FC<QuestionnaireQuestionFormProps> = ({
                 }
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/InjectSpecification/QuestionnaireForm/index.tsx b/frontend/src/editor/InjectSpecification/QuestionnaireForm/index.tsx
index 57788542345fa39b222ebf5081c779f4b0ac5182..3f7573b9ed49710d7aab635d29b8f42b66eee4b2 100644
--- a/frontend/src/editor/InjectSpecification/QuestionnaireForm/index.tsx
+++ b/frontend/src/editor/InjectSpecification/QuestionnaireForm/index.tsx
@@ -1,6 +1,7 @@
+import SaveButtonGroup from '@/editor/SaveButtonGroup'
 import TooltipLabel from '@/editor/Tooltips/TooltipLabel'
 import { QUESTIONNAIRE_FORM } from '@/editor/assets/pageContent/injectSpecification'
-import { Button, InputGroup } from '@blueprintjs/core'
+import { InputGroup } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
@@ -15,9 +16,15 @@ import QuestionnaireQuestions from './QuestionnaireQuestions'
 
 interface QuestionnaireFormProps {
   injectInfoId: number
+  changed: boolean
+  onChangedChange: (value: boolean) => void
 }
 
-const QuestionnaireForm: FC<QuestionnaireFormProps> = ({ injectInfoId }) => {
+const QuestionnaireForm: FC<QuestionnaireFormProps> = ({
+  injectInfoId,
+  changed,
+  onChangedChange,
+}) => {
   const questionnaire = useLiveQuery(
     () => getQuestionnaireByInjectInfoId(injectInfoId),
     [injectInfoId],
@@ -34,7 +41,7 @@ const QuestionnaireForm: FC<QuestionnaireFormProps> = ({ injectInfoId }) => {
     }
   }, [questionnaire, injectInfoId])
 
-  const handleUpdateButton = useCallback(
+  const updateInject = useCallback(
     async (newQuestionnaire: Questionnaire | Omit<Questionnaire, 'id'>) => {
       try {
         if (questionnaire) {
@@ -54,6 +61,19 @@ const QuestionnaireForm: FC<QuestionnaireFormProps> = ({ injectInfoId }) => {
     [notify, questionnaire]
   )
 
+  const handleUpdate = useCallback(async () => {
+    if (!changed) onChangedChange(true)
+    await updateInject({
+      injectInfoId,
+      title,
+    })
+    onChangedChange(false)
+  }, [changed, injectInfoId, title, updateInject])
+
+  useEffect(() => {
+    if (changed) handleUpdate()
+  }, [changed, handleUpdate])
+
   return (
     <div>
       <TooltipLabel label={QUESTIONNAIRE_FORM.title}>
@@ -78,16 +98,10 @@ const QuestionnaireForm: FC<QuestionnaireFormProps> = ({ injectInfoId }) => {
           />
         </>
       )}
-      <Button
-        onClick={() =>
-          handleUpdateButton({
-            injectInfoId,
-            title,
-          })
-        }
-        intent='primary'
-        icon='edit'
-        text='Save changes'
+      <SaveButtonGroup
+        isValid
+        handleUpdate={() => handleUpdate()}
+        prevPath='/editor/create/inject-specification'
       />
     </div>
   )
diff --git a/frontend/src/editor/InjectSpecification/index.tsx b/frontend/src/editor/InjectSpecification/index.tsx
index cc1681a52ad7d3e2b0f00a6a22068a2a7c27b549..db50cf5549c0bda54d722fd0aebf58c8ef3ac088 100644
--- a/frontend/src/editor/InjectSpecification/index.tsx
+++ b/frontend/src/editor/InjectSpecification/index.tsx
@@ -1,6 +1,13 @@
-import { NonIdealState, Tab, Tabs, TabsExpander } from '@blueprintjs/core'
+import {
+  Divider,
+  NonIdealState,
+  Tab,
+  Tabs,
+  TabsExpander,
+} from '@blueprintjs/core'
+import { css } from '@emotion/css'
 import { useLiveQuery } from 'dexie-react-hooks'
-import { memo, type FC } from 'react'
+import { memo, useState, type FC } from 'react'
 import InjectForm from '../InjectForm'
 import { getInjectInfoById } from '../indexeddb/operations'
 import type { InjectInfo } from '../indexeddb/types'
@@ -11,6 +18,10 @@ import InformationInjectForm from './InformationInjectForm'
 import OverlayForm from './OverlayForm'
 import QuestionnaireForm from './QuestionnaireForm'
 
+const divider = css`
+  margin: 1.5rem 0 1rem;
+`
+
 interface InjectSpecificationProps {
   injectInfoId: number
 }
@@ -23,6 +34,7 @@ const InjectSpecification: FC<InjectSpecificationProps> = ({
     [injectInfoId],
     null
   ) as InjectInfo
+  const [changed, setChanged] = useState(false)
 
   if (!injectInfo) {
     return (
@@ -37,9 +49,15 @@ const InjectSpecification: FC<InjectSpecificationProps> = ({
   return (
     <div>
       <div style={{ marginBottom: '1rem' }}>
-        <p>Name: {injectInfo.name}</p>
-        <p>Description: {injectInfo.description}</p>
-        <p>Type: {injectInfo.type}</p>
+        <p>
+          <b>Name:</b> {injectInfo.name}
+        </p>
+        <p>
+          <b>Description:</b> {injectInfo.description}
+        </p>
+        <p>
+          <b>Type:</b> {injectInfo.type}
+        </p>
         <InjectForm
           inject={injectInfo}
           buttonProps={{
@@ -49,6 +67,7 @@ const InjectSpecification: FC<InjectSpecificationProps> = ({
           }}
         />
       </div>
+      <Divider className={divider} />
       <Tabs>
         <Tab
           id='parameters'
@@ -56,13 +75,25 @@ const InjectSpecification: FC<InjectSpecificationProps> = ({
           panel={
             <>
               {injectInfo.type === InjectType.EMAIL && (
-                <EmailInjectForm injectInfoId={injectInfoId} />
+                <EmailInjectForm
+                  injectInfoId={injectInfoId}
+                  changed={changed}
+                  onChangedChange={value => setChanged(value)}
+                />
               )}
               {injectInfo.type === InjectType.INFORMATION && (
-                <InformationInjectForm injectInfoId={injectInfoId} />
+                <InformationInjectForm
+                  injectInfoId={injectInfoId}
+                  changed={changed}
+                  onChangedChange={value => setChanged(value)}
+                />
               )}
               {injectInfo.type === InjectType.QUESTIONNAIRE && (
-                <QuestionnaireForm injectInfoId={injectInfoId} />
+                <QuestionnaireForm
+                  injectInfoId={injectInfoId}
+                  changed={changed}
+                  onChangedChange={value => setChanged(value)}
+                />
               )}
             </>
           }
@@ -74,13 +105,21 @@ const InjectSpecification: FC<InjectSpecificationProps> = ({
             <ConnectionsForm
               injectInfoId={injectInfoId}
               injectType={injectInfo.type}
+              changed={changed}
+              onChangedChange={value => setChanged(value)}
             />
           }
         />
         <Tab
           id='overlay'
           title='Overlay'
-          panel={<OverlayForm injectInfoId={injectInfoId} />}
+          panel={
+            <OverlayForm
+              injectInfoId={injectInfoId}
+              changed={changed}
+              onChangedChange={value => setChanged(value)}
+            />
+          }
         />
         <TabsExpander />
       </Tabs>
diff --git a/frontend/src/editor/InjectsOverview/Inject.tsx b/frontend/src/editor/InjectsOverview/Inject.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ba65779e15ec7b37b432a96509944b0839e8c5b5
--- /dev/null
+++ b/frontend/src/editor/InjectsOverview/Inject.tsx
@@ -0,0 +1,54 @@
+import type { InjectInfo, Milestone } from '@/editor/indexeddb/types'
+import { useNavigate } from '@/router'
+import { useLiveQuery } from 'dexie-react-hooks'
+import type { FC } from 'react'
+import { memo, useMemo } from 'react'
+import OverviewCard from '../OverviewCard'
+import {
+  doesInjectHaveCorrectCondition,
+  isInjectSpecified,
+} from '../indexeddb/operations'
+import { getInjectIcon } from '../utils'
+
+interface InjectProps {
+  injectInfo: InjectInfo
+  milestones: Milestone[]
+}
+
+const Inject: FC<InjectProps> = ({ injectInfo, milestones }) => {
+  const isFilled = useLiveQuery(() => isInjectSpecified(injectInfo), [], true)
+  const isValid = useLiveQuery(
+    () => doesInjectHaveCorrectCondition(injectInfo.id, milestones),
+    [injectInfo.id, milestones],
+    true
+  )
+  const nav = useNavigate()
+
+  const isSpecified = useMemo(() => isFilled && isValid, [isFilled, isValid])
+  const tooltipContent = useMemo(
+    () =>
+      [
+        !isFilled && 'Missing mandatory parameters',
+        !isValid && 'Incorrect milestone condition',
+      ]
+        .filter(Boolean)
+        .join(', '),
+    [isFilled, isValid]
+  )
+
+  return (
+    <OverviewCard
+      name={injectInfo.name}
+      icon={getInjectIcon(injectInfo)}
+      onClick={() =>
+        nav(`/editor/create/inject-specification/:injectId`, {
+          params: { injectId: injectInfo.id.toString() },
+        })
+      }
+      isSpecified={isSpecified}
+      tooltipContent={tooltipContent}
+    />
+  )
+}
+
+export default memo(Inject)
diff --git a/frontend/src/editor/InjectsOverview/index.tsx b/frontend/src/editor/InjectsOverview/index.tsx
index 5c24961f860cd3b6205987c97cb66d9f3d6b4da0..81ca06ba81f089b3926e72692f84373a0fa9fc5a 100644
--- a/frontend/src/editor/InjectsOverview/index.tsx
+++ b/frontend/src/editor/InjectsOverview/index.tsx
@@ -1,33 +1,22 @@
 import { db } from '@/editor/indexeddb/db'
 import type { InjectInfo } from '@/editor/indexeddb/types'
-import { useNavigate } from '@/router'
-import { Card, CardList, Icon } from '@blueprintjs/core'
+import { CardList } from '@blueprintjs/core'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo } from 'react'
-import { getInjectIcon } from '../utils'
+import Inject from './Inject'
 
 const InjectsOverview = () => {
   const injectInfos = useLiveQuery(() => db.injectInfos.toArray(), [], [])
-  const nav = useNavigate()
+  const milestones = useLiveQuery(() => db.milestones.toArray(), [], [])
 
   return (
     <CardList>
       {injectInfos?.map((injectInfo: InjectInfo) => (
-        <Card
-          interactive
+        <Inject
           key={injectInfo.id}
-          onClick={() =>
-            nav(`/editor/create/inject-specification/:injectId`, {
-              params: { injectId: injectInfo.id.toString() },
-            })
-          }
-        >
-          <Icon
-            icon={getInjectIcon(injectInfo)}
-            style={{ marginRight: '1rem' }}
-          />
-          {injectInfo.name}
-        </Card>
+          injectInfo={injectInfo}
+          milestones={milestones}
+        />
       ))}
     </CardList>
   )
diff --git a/frontend/src/editor/LandingPage/index.tsx b/frontend/src/editor/LandingPage/index.tsx
index a9838bce27bd650b7401ed33228211f360003da4..2b384e04c6e1d96ecf1190c08d8f5d098b595039 100644
--- a/frontend/src/editor/LandingPage/index.tsx
+++ b/frontend/src/editor/LandingPage/index.tsx
@@ -7,22 +7,43 @@ import { isEmpty } from 'lodash'
 import { useEffect, useState } from 'react'
 import DataRemovalDialog from '../DataRemovalDialog'
 import DefinitionUploader from '../DefinitionUploader'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { LANDING_PAGE_ACTIONS } from '../assets/pageContent/landingPage'
 import { PAGE_INFORMATION } from '../assets/pageInformation'
 import { isDbEmpty } from '../indexeddb/operations'
 import { PageNames } from '../types'
 import useEditorStorage from '../useEditorStorage'
 
+const logo = css`
+  width: 100%;
+  height: 200px;
+  margin: auto;
+`
+
+const container = css`
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-around;
+`
+
 const introduction = css`
   display: flex;
   flex-direction: column;
+  align-items: center;
   text-align: center;
-  gap: 1rem;
-  margin: 0 auto;
-  max-width: 200px;
+  gap: 0.5rem;
+  max-width: 400px;
   width: 100%;
 `
 
+const buttonGroup = css`
+  display: flex;
+  flex-direction: column;
+  gap: 0.5rem;
+  width: 200px;
+`
+
 const LandingPage = () => {
   const nav = useNavigate()
   const [config] = useEditorStorage()
@@ -38,51 +59,52 @@ const LandingPage = () => {
   }, [config])
 
   return (
-    <Container makeFullHeight>
-      <InjectLogo
-        style={{
-          width: '100%',
-          height: '200px',
-          margin: 'auto',
-        }}
-      />
-      <div className={introduction}>
-        <h1>{PAGE_INFORMATION[PageNames.LANDING_PAGE].title}</h1>
-        <p
-          dangerouslySetInnerHTML={{
-            __html: PAGE_INFORMATION[PageNames.LANDING_PAGE].description,
-          }}
-        />
-        <Button
-          type='button'
-          intent='primary'
-          icon={isDataEmpty ? 'plus' : 'arrow-right'}
-          onClick={() => nav('/editor/create/introduction')}
-        >
-          {isDataEmpty
-            ? LANDING_PAGE_ACTIONS.create
-            : LANDING_PAGE_ACTIONS.edit}
-        </Button>
-        {!isDataEmpty && (
-          <DataRemovalDialog
-            openButtonProps={{
-              icon: 'plus',
-              text: LANDING_PAGE_ACTIONS.create,
-            }}
-            confirmButtonProps={{
-              intent: 'primary',
-              icon: 'tick',
-              text: 'Confirm',
+    <Container makeFullHeight className={container}>
+      <div className={container}>
+        <InjectLogo className={logo} />
+        <div className={introduction}>
+          <h1 style={{ margin: '0' }}>
+            {PAGE_INFORMATION[PageNames.LANDING_PAGE].title}
+          </h1>
+          <p
+            dangerouslySetInnerHTML={{
+              __html: PAGE_INFORMATION[PageNames.LANDING_PAGE].description,
             }}
-            redirectTo='/editor/create/introduction'
+            style={{ marginBottom: '1.5rem' }}
           />
-        )}
-        <DefinitionUploader />
-        <Button
-          text={LANDING_PAGE_ACTIONS.select}
-          icon='list-detail-view'
-          onClick={() => nav('/editor/definitions')}
-        />
+          <div className={buttonGroup}>
+            <Button
+              type='button'
+              intent='primary'
+              icon={isDataEmpty ? 'plus' : 'arrow-right'}
+              onClick={() => nav('/editor/create/introduction')}
+            >
+              {isDataEmpty
+                ? LANDING_PAGE_ACTIONS.create
+                : LANDING_PAGE_ACTIONS.edit}
+            </Button>
+            {!isDataEmpty && (
+              <DataRemovalDialog
+                openButtonProps={{
+                  icon: 'plus',
+                  text: LANDING_PAGE_ACTIONS.create,
+                }}
+                confirmButtonProps={{
+                  intent: 'primary',
+                  icon: 'tick',
+                  text: GENERIC_CONTENT.buttons.confirm,
+                }}
+                redirectTo='/editor/create/introduction'
+              />
+            )}
+            <DefinitionUploader />
+            <Button
+              text={LANDING_PAGE_ACTIONS.select}
+              icon='list-detail-view'
+              onClick={() => nav('/editor/definitions')}
+            />
+          </div>
+        </div>
       </div>
     </Container>
   )
diff --git a/frontend/src/editor/LearningActivitiesOverview/LearningActivity.tsx b/frontend/src/editor/LearningActivitiesOverview/LearningActivity.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7375533f7d7e64ddf1395017f327c5882da7cf5d
--- /dev/null
+++ b/frontend/src/editor/LearningActivitiesOverview/LearningActivity.tsx
@@ -0,0 +1,61 @@
+import type { LearningActivityInfo, Milestone } from '@/editor/indexeddb/types'
+import { useNavigate } from '@/router'
+import { useLiveQuery } from 'dexie-react-hooks'
+import type { FC } from 'react'
+import { memo, useMemo } from 'react'
+import OverviewCard from '../OverviewCard'
+import {
+  doesLearningActivityHaveCorrectCondition,
+  isActivitySpecified,
+} from '../indexeddb/operations'
+import { getLearningActivityIcon } from '../utils'
+
+interface LearningActivityProps {
+  activity: LearningActivityInfo
+  milestones: Milestone[]
+}
+
+const LearningActivity: FC<LearningActivityProps> = ({
+  activity,
+  milestones,
+}) => {
+  const isFilled = useLiveQuery(
+    () => isActivitySpecified(activity),
+    [activity],
+    true
+  )
+  const isValid = useLiveQuery(
+    () => doesLearningActivityHaveCorrectCondition(activity, milestones),
+    [activity, milestones],
+    true
+  )
+  const nav = useNavigate()
+
+  const isSpecified = useMemo(() => isFilled && isValid, [isFilled, isValid])
+  const tooltipContent = useMemo(
+    () =>
+      [
+        !isFilled && 'Missing mandatory parameters',
+        !isValid && 'Incorrect milestone condition',
+      ]
+        .filter(Boolean)
+        .join(', '),
+    [isFilled, isValid]
+  )
+
+  return (
+    <OverviewCard
+      name={activity.name}
+      icon={getLearningActivityIcon(activity)}
+      onClick={() =>
+        nav(`/editor/create/activity-specification/:activityId`, {
+          params: { activityId: activity.id.toString() },
+        })
+      }
+      isSpecified={isSpecified}
+      tooltipContent={tooltipContent}
+    />
+  )
+}
+
+export default memo(LearningActivity)
diff --git a/frontend/src/editor/LearningActivitiesOverview/index.tsx b/frontend/src/editor/LearningActivitiesOverview/index.tsx
index c973fec920f7e44ce9485d98ca4e1927c5552bce..b031faee6a88604b37753ea893b2d5ed13c4a044 100644
--- a/frontend/src/editor/LearningActivitiesOverview/index.tsx
+++ b/frontend/src/editor/LearningActivitiesOverview/index.tsx
@@ -1,10 +1,9 @@
 import { db } from '@/editor/indexeddb/db'
 import type { LearningActivityInfo } from '@/editor/indexeddb/types'
-import { useNavigate } from '@/router'
-import { Card, CardList, Icon } from '@blueprintjs/core'
+import { CardList } from '@blueprintjs/core'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo } from 'react'
-import { getLearningActivityIcon } from '../utils'
+import LearningActivity from './LearningActivity'
 
 const LearningActivitiesOverview = () => {
   const learningActivities = useLiveQuery(
@@ -12,26 +11,16 @@ const LearningActivitiesOverview = () => {
     [],
     []
   )
-  const nav = useNavigate()
+  const milestones = useLiveQuery(() => db.milestones.toArray(), [], [])
 
   return (
     <CardList>
       {learningActivities?.map((activity: LearningActivityInfo) => (
-        <Card
-          interactive
+        <LearningActivity
           key={activity.id}
-          onClick={() =>
-            nav(`/editor/create/activity-specification/:activityId`, {
-              params: { activityId: activity.id.toString() },
-            })
-          }
-        >
-          <Icon
-            icon={getLearningActivityIcon(activity)}
-            style={{ marginRight: '1rem' }}
-          />
-          {activity.name}
-        </Card>
+          activity={activity}
+          milestones={milestones}
+        />
       ))}
     </CardList>
   )
diff --git a/frontend/src/editor/LearningActivityForm/index.tsx b/frontend/src/editor/LearningActivityForm/index.tsx
index b23f176d2177c0ba9e57fe0c0f36b10be621d720..c88284299d8e4d926adb37777813892cb1a9dc79 100644
--- a/frontend/src/editor/LearningActivityForm/index.tsx
+++ b/frontend/src/editor/LearningActivityForm/index.tsx
@@ -16,6 +16,7 @@ import { notify } from '@inject/shared/notification/engine'
 import type { FC } from 'react'
 import { memo, useCallback, useEffect, useState } from 'react'
 import TooltipLabel from '../Tooltips/TooltipLabel'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { LEARNING_ACTIVITY_FORM } from '../assets/pageContent/learningObjectives'
 import type { LearningActivityInfo } from '../indexeddb/types'
 import { LearningActivityType } from '../indexeddb/types'
@@ -140,7 +141,7 @@ const LearningActivityForm: FC<LearningActivityFormProps> = ({
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -155,7 +156,7 @@ const LearningActivityForm: FC<LearningActivityFormProps> = ({
                 }
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/LearningActivitySpecification/EmailTemplateForm.tsx b/frontend/src/editor/LearningActivitySpecification/EmailTemplateForm.tsx
index 1b186953890709eacdc3a663fe6dd18c889d189a..5c0b05a73465ad06ab9960a9b98dc0db11a630d8 100644
--- a/frontend/src/editor/LearningActivitySpecification/EmailTemplateForm.tsx
+++ b/frontend/src/editor/LearningActivitySpecification/EmailTemplateForm.tsx
@@ -1,8 +1,8 @@
-import { Button } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
 import EmailTemplateFormContent from '../EmailTemplateFormContent'
+import SaveButtonGroup from '../SaveButtonGroup'
 import {
   addEmailTemplate,
   getEmailTemplateByActivityId,
@@ -64,8 +64,9 @@ const EmailTemplateForm: FC<EmailTemplateFormProps> = ({
         onEmailAddressIdChange={(value: number) => setSelectedAddressId(value)}
         onFileIdChange={(value: number) => setFileId(value)}
       />
-      <Button
-        onClick={() =>
+      <SaveButtonGroup
+        isValid
+        handleUpdate={() =>
           handleUpdateButton({
             learningActivityId,
             context,
@@ -74,9 +75,7 @@ const EmailTemplateForm: FC<EmailTemplateFormProps> = ({
             fileId,
           })
         }
-        intent='primary'
-        icon='edit'
-        text='Save changes'
+        prevPath='/editor/create/activity-specification'
       />
     </div>
   )
diff --git a/frontend/src/editor/LearningActivitySpecification/ToolResponseForm.tsx b/frontend/src/editor/LearningActivitySpecification/ToolResponseForm.tsx
index fcec134d939bee0262f8cb0148baad23730ab941..a67e69e22f6bab1487d3069705f73c943817db8c 100644
--- a/frontend/src/editor/LearningActivitySpecification/ToolResponseForm.tsx
+++ b/frontend/src/editor/LearningActivitySpecification/ToolResponseForm.tsx
@@ -1,8 +1,8 @@
-import { Button } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
 import useValidateExpression from '../ExpressionBuilder/useValidateExpression'
+import SaveButtonGroup from '../SaveButtonGroup'
 import ToolResponseFormContent from '../ToolResponseFormContent'
 import {
   addToolResponse,
@@ -80,9 +80,9 @@ const ToolResponseForm: FC<ToolResponseFormProps> = ({
           setMilestoneCondition(value)
         }
       />
-      <Button
-        disabled={!isValid}
-        onClick={() =>
+      <SaveButtonGroup
+        isValid={isValid}
+        handleUpdate={() =>
           handleUpdateButton({
             learningActivityId,
             parameter,
@@ -94,9 +94,7 @@ const ToolResponseForm: FC<ToolResponseFormProps> = ({
             milestoneCondition: isValid ? milestoneCondition : [],
           })
         }
-        intent='primary'
-        icon='edit'
-        text='Save changes'
+        prevPath='/editor/create/activity-specification'
       />
     </div>
   )
diff --git a/frontend/src/editor/LearningActivitySpecification/index.tsx b/frontend/src/editor/LearningActivitySpecification/index.tsx
index e99315e95c74247df99a0fb717fd90432999397a..b6b4562674f70d2d111e85915be2e2576b7edef7 100644
--- a/frontend/src/editor/LearningActivitySpecification/index.tsx
+++ b/frontend/src/editor/LearningActivitySpecification/index.tsx
@@ -1,4 +1,5 @@
 import { Divider, NonIdealState } from '@blueprintjs/core'
+import { css } from '@emotion/css'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { memo, type FC } from 'react'
 import LearningActivityForm from '../LearningActivityForm'
@@ -8,6 +9,10 @@ import { LearningActivityType } from '../indexeddb/types'
 import EmailTemplateForm from './EmailTemplateForm'
 import ToolResponseForm from './ToolResponseForm'
 
+const divider = css`
+  margin: 1.5rem 0 1rem;
+`
+
 interface LearningActivitySpecificationProps {
   learningActivityId: number
 }
@@ -34,9 +39,15 @@ const LearningActivitySpecification: FC<LearningActivitySpecificationProps> = ({
   return (
     <div>
       <div>
-        <p>Name: {activity.name}</p>
-        <p>Description: {activity.description}</p>
-        <p>Type: {activity.type}</p>
+        <p>
+          <b>Name:</b> {activity.name}
+        </p>
+        <p>
+          <b>Description:</b> {activity.description}
+        </p>
+        <p>
+          <b>Type:</b> {activity.type}
+        </p>
         <LearningActivityForm
           learningActivity={activity}
           learningObjectiveId={activity.learningObjectiveId}
@@ -47,7 +58,7 @@ const LearningActivitySpecification: FC<LearningActivitySpecificationProps> = ({
           }}
         />
       </div>
-      <Divider style={{ margin: '1rem 0' }} />
+      <Divider className={divider} />
       {activity.type === LearningActivityType.TOOL && (
         <ToolResponseForm learningActivityId={learningActivityId} />
       )}
diff --git a/frontend/src/editor/LearningObjectiveForm/index.tsx b/frontend/src/editor/LearningObjectiveForm/index.tsx
index 6297d0ccd6e3e06e51da22d059d65d2796200a51..4a65cf7f138616961809f9e9aa6f9dc9d2e8ab21 100644
--- a/frontend/src/editor/LearningObjectiveForm/index.tsx
+++ b/frontend/src/editor/LearningObjectiveForm/index.tsx
@@ -14,6 +14,7 @@ import { notify } from '@inject/shared/notification/engine'
 import type { FC } from 'react'
 import { memo, useCallback, useEffect, useState } from 'react'
 import TooltipLabel from '../Tooltips/TooltipLabel'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { LEARNING_OBJECTIVE_FORM } from '../assets/pageContent/learningObjectives'
 import type { LearningObjectiveInfo } from '../indexeddb/types'
 
@@ -104,7 +105,7 @@ const LearningObjectiveForm: FC<LearningObjectiveFormProps> = ({
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -112,7 +113,7 @@ const LearningObjectiveForm: FC<LearningObjectiveFormProps> = ({
                 onClick={() => handleAddButton({ name })}
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/Navbar/NavbarButton.tsx b/frontend/src/editor/Navbar/NavbarButton.tsx
index e2ba6d0d977b6f1c7870bea92cbeca3365e56bb7..6ec54c0ae4fee746371917db7a66dac46ddc7e06 100644
--- a/frontend/src/editor/Navbar/NavbarButton.tsx
+++ b/frontend/src/editor/Navbar/NavbarButton.tsx
@@ -1,25 +1,64 @@
 import type { Path } from '@/router'
 import { useNavigate } from '@/router'
-import { Button } from '@blueprintjs/core'
-import type { FC } from 'react'
+import { Button, Icon, Intent } from '@blueprintjs/core'
+import { css } from '@emotion/css'
+import { useMemo, type FC } from 'react'
+import { useLocation } from 'react-router-dom'
 import { NAVIGATION_CONTENT } from '../assets/navigationContent'
 import type { NavigationLinkNames } from '../types'
 
+const buttonContent = css`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+`
+
 interface NavbarButtonProps {
   path: Path
   linkKey: NavigationLinkNames
   visible?: boolean
+  filled?: boolean
 }
 
-const NavbarButton: FC<NavbarButtonProps> = ({ path, linkKey, visible }) => {
+const NavbarButton: FC<NavbarButtonProps> = ({
+  path,
+  linkKey,
+  visible,
+  filled,
+}) => {
   const nav = useNavigate()
+  const { pathname } = useLocation()
   const name = NAVIGATION_CONTENT.links[linkKey]
 
+  const active = useMemo(
+    () =>
+      path === pathname ||
+      (path === '/editor/create/other' &&
+        (pathname === '/editor/create/tools' ||
+          pathname === '/editor/create/emails')) ||
+      ((path === '/editor/create/activity-specification' ||
+        path === '/editor/create/inject-specification') &&
+        pathname.startsWith(path)),
+    [path, pathname]
+  )
+
+  const highlighted = useMemo(() => !filled && !active, [filled, active])
+
   if (!visible) return
 
   return (
-    <Button type='button' onClick={() => nav(path)} alignText='left' minimal>
-      {name}
+    <Button
+      type='button'
+      onClick={() => nav(path)}
+      alignText='left'
+      minimal
+      active={active}
+      intent={highlighted ? Intent.WARNING : Intent.NONE}
+    >
+      <div className={buttonContent}>
+        <span>{name}</span>
+        {highlighted && <Icon icon='high-priority' />}
+      </div>
     </Button>
   )
 }
diff --git a/frontend/src/editor/Navbar/index.tsx b/frontend/src/editor/Navbar/index.tsx
index b834ba35e7e1d8b10e87754f800ef2c56a1a10a0..24eed3f4a8f4bc47b3a0d5ac109d02e073b48d3d 100644
--- a/frontend/src/editor/Navbar/index.tsx
+++ b/frontend/src/editor/Navbar/index.tsx
@@ -1,4 +1,13 @@
+import { useLiveQuery } from 'dexie-react-hooks'
 import { memo } from 'react'
+import {
+  areActivitiesSpecified,
+  areInjectsSpecified,
+  doInjectsHaveCorrectConditions,
+  doLearningActivitiesHaveCorrectConditions,
+  doToolResponsesHaveCorrectConditions,
+  doToolsHaveResponses,
+} from '../indexeddb/operations'
 import { NavigationLinkNames } from '../types'
 import useEditorAccessStorage from '../useEditorAccessStorage'
 import NavbarButton from './NavbarButton'
@@ -6,57 +15,96 @@ import NavbarButton from './NavbarButton'
 const Navbar = () => {
   const [access] = useEditorAccessStorage()
 
+  const activitiesSpecified = useLiveQuery(
+    () => areActivitiesSpecified(),
+    [],
+    false
+  )
+  const activitiesHaveCorrectConditions = useLiveQuery(
+    () => doLearningActivitiesHaveCorrectConditions(),
+    [],
+    false
+  )
+
+  const injectsSpecified = useLiveQuery(() => areInjectsSpecified(), [], false)
+  const injectsHaveCorrectConditions = useLiveQuery(
+    () => doInjectsHaveCorrectConditions(),
+    [],
+    false
+  )
+
+  const toolsHaveResponses = useLiveQuery(
+    () => doToolsHaveResponses(),
+    [],
+    false
+  )
+  const toolsHaveCorrectConditions = useLiveQuery(
+    () => doToolResponsesHaveCorrectConditions(),
+    [],
+    false
+  )
+
   return (
     <div style={{ display: 'flex', flexDirection: 'column' }}>
       <NavbarButton
         path='/editor/create/introduction'
         linkKey={NavigationLinkNames.INTRODUCTION}
         visible
+        filled={access?.introductionFilled}
       />
       <NavbarButton
         path='/editor/create/exercise-information'
         linkKey={NavigationLinkNames.EXERCISE_INFORMATION}
         visible={access?.introductionFilled}
+        filled={access?.exerciseInformationFilled}
       />
       <NavbarButton
         path='/editor/create/learning-objectives'
         linkKey={NavigationLinkNames.LEARNING_OBJECTIVES}
         visible={access?.exerciseInformationFilled}
+        filled={access?.objectivesFilled}
       />
       <NavbarButton
         path='/editor/create/injects'
         linkKey={NavigationLinkNames.INJECTS}
         visible={access?.objectivesFilled}
+        filled={access?.injectsFilled}
       />
       <NavbarButton
         path='/editor/create/activity-specification'
         linkKey={NavigationLinkNames.ACTIVITY_SPECIFICATION_OVERVIEW}
         visible={access?.injectsFilled}
+        filled={activitiesSpecified && activitiesHaveCorrectConditions}
       />
       <NavbarButton
         path='/editor/create/inject-specification'
         linkKey={NavigationLinkNames.INJECT_SPECIFICATION_OVERVIEW}
         visible={access?.injectsFilled}
+        filled={injectsSpecified && injectsHaveCorrectConditions}
       />
       <NavbarButton
         path='/editor/create/other'
         linkKey={NavigationLinkNames.OTHER}
         visible={access?.injectsFilled}
+        filled={toolsHaveResponses && toolsHaveCorrectConditions}
       />
       <NavbarButton
         path='/editor/create/final-information'
         linkKey={NavigationLinkNames.FINAL_INFORMATION}
         visible={access?.specificationsFilled}
+        filled={access?.finalInformationFilled}
       />
       <NavbarButton
         path='/editor/create/conclusion'
         linkKey={NavigationLinkNames.CONCLUSION}
         visible={access?.finalInformationFilled}
+        filled={access?.conclusionFilled}
       />
       <NavbarButton
         path='/editor/create/save'
         linkKey={NavigationLinkNames.DOWNLOAD}
         visible={access?.conclusionFilled}
+        filled
       />
     </div>
   )
diff --git a/frontend/src/editor/OverviewCard/index.tsx b/frontend/src/editor/OverviewCard/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d8679eb0a1e0975304b4df480bc4f2df6aacf07d
--- /dev/null
+++ b/frontend/src/editor/OverviewCard/index.tsx
@@ -0,0 +1,40 @@
+import type { IconName } from '@blueprintjs/core'
+import { Card, Icon, Tooltip } from '@blueprintjs/core'
+import { css } from '@emotion/css'
+import type { FC } from 'react'
+import { memo } from 'react'
+
+const overviewCard = css`
+  display: flex;
+  justify-content: space-between;
+`
+
+interface OverviewCardProps {
+  name: string
+  icon?: IconName
+  onClick: () => void
+  isSpecified?: boolean
+  tooltipContent: string
+}
+
+const OverviewCard: FC<OverviewCardProps> = ({
+  name,
+  icon,
+  onClick,
+  isSpecified,
+  tooltipContent,
+}) => (
+  <Card interactive onClick={onClick} className={overviewCard}>
+    <span>
+      {icon && <Icon icon={icon} style={{ marginRight: '1rem' }} />}
+      {name}
+    </span>
+    {!isSpecified && (
+      <Tooltip content={tooltipContent}>
+        <Icon icon='high-priority' intent='warning' size={20} />
+      </Tooltip>
+    )}
+  </Card>
+)
+
+export default memo(OverviewCard)
diff --git a/frontend/src/editor/SaveButtonGroup/index.tsx b/frontend/src/editor/SaveButtonGroup/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..bc7fad86592f740f94ec3babf0161c65547c1f36
--- /dev/null
+++ b/frontend/src/editor/SaveButtonGroup/index.tsx
@@ -0,0 +1,43 @@
+import type { Path } from '@/router'
+import { useNavigate } from '@/router'
+import { Button, ButtonGroup } from '@blueprintjs/core'
+import { memo, type FC } from 'react'
+import { GENERIC_CONTENT } from '../assets/generalContent'
+
+interface SaveButtonGroupProps {
+  isValid: boolean
+  handleUpdate: () => void
+  prevPath: Path
+}
+
+const SaveButtonGroup: FC<SaveButtonGroupProps> = ({
+  isValid,
+  handleUpdate,
+  prevPath,
+}) => {
+  const nav = useNavigate()
+
+  return (
+    <ButtonGroup>
+      <Button
+        disabled={!isValid}
+        onClick={() => {
+          handleUpdate()
+          nav(prevPath)
+        }}
+        intent='primary'
+        icon='arrow-left'
+        text={GENERIC_CONTENT.buttons.saveAndExit}
+        style={{ marginRight: '0.5rem' }}
+      />
+      <Button
+        disabled={!isValid}
+        onClick={() => handleUpdate()}
+        icon='edit'
+        text={GENERIC_CONTENT.buttons.save}
+      />
+    </ButtonGroup>
+  )
+}
+
+export default memo(SaveButtonGroup)
diff --git a/frontend/src/editor/ToolForm/index.tsx b/frontend/src/editor/ToolForm/index.tsx
index 30d4bb7b769facd0d0df2265d60aadf56162026d..5127389ca25760bc1d0b7d53997112929394c51e 100644
--- a/frontend/src/editor/ToolForm/index.tsx
+++ b/frontend/src/editor/ToolForm/index.tsx
@@ -12,6 +12,7 @@ import { notify } from '@inject/shared/notification/engine'
 import type { FC } from 'react'
 import { memo, useCallback, useEffect, useState } from 'react'
 import TooltipLabel from '../Tooltips/TooltipLabel'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { TOOL_FORM } from '../assets/pageContent/tools'
 import type { ToolInfo } from '../indexeddb/types'
 
@@ -24,14 +25,12 @@ interface ToolFormProps {
 const ToolForm: FC<ToolFormProps> = ({ toolInfo, buttonProps, onAdd }) => {
   const [isOpen, setIsOpen] = useState(false)
   const [name, setName] = useState<string>('')
-  const [category, setCategory] = useState<string>('')
   const [tooltipDescription, setTooltipDescription] = useState<string>('')
   const [hint, setHint] = useState<string>('')
   const [defaultResponse, setDefaultResponse] = useState<string>('')
 
   const clearInput = useCallback(() => {
     setName('')
-    setCategory('')
     setTooltipDescription('')
     setHint('')
     setDefaultResponse('')
@@ -69,7 +68,6 @@ const ToolForm: FC<ToolFormProps> = ({ toolInfo, buttonProps, onAdd }) => {
 
   useEffect(() => {
     setName(toolInfo?.name || '')
-    setCategory(toolInfo?.category || '')
     setTooltipDescription(toolInfo?.tooltipDescription || '')
     setHint(toolInfo?.hint || '')
     setDefaultResponse(toolInfo?.defaultResponse || '')
@@ -92,13 +90,6 @@ const ToolForm: FC<ToolFormProps> = ({ toolInfo, buttonProps, onAdd }) => {
               onChange={e => setName(e.target.value)}
             />
           </TooltipLabel>
-          <TooltipLabel label={TOOL_FORM.category}>
-            <InputGroup
-              placeholder='Input text'
-              value={category}
-              onChange={e => setCategory(e.target.value)}
-            />
-          </TooltipLabel>
           <TooltipLabel label={TOOL_FORM.tooltip}>
             <InputGroup
               placeholder='Input text'
@@ -136,7 +127,6 @@ const ToolForm: FC<ToolFormProps> = ({ toolInfo, buttonProps, onAdd }) => {
                   handleUpdateButton({
                     id: toolInfo.id,
                     name,
-                    category,
                     tooltipDescription,
                     hint,
                     defaultResponse,
@@ -144,7 +134,7 @@ const ToolForm: FC<ToolFormProps> = ({ toolInfo, buttonProps, onAdd }) => {
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -152,7 +142,6 @@ const ToolForm: FC<ToolFormProps> = ({ toolInfo, buttonProps, onAdd }) => {
                 onClick={() =>
                   handleAddButton({
                     name,
-                    category,
                     tooltipDescription,
                     hint,
                     defaultResponse,
@@ -160,7 +149,7 @@ const ToolForm: FC<ToolFormProps> = ({ toolInfo, buttonProps, onAdd }) => {
                 }
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/ToolResponseFormDialog/index.tsx b/frontend/src/editor/ToolResponseFormDialog/index.tsx
index 84086419cddc5d3bb688cc3ad1b9b8a7074d052b..b8607c574c6aa7f7ace4f8604cf4836bcd839849 100644
--- a/frontend/src/editor/ToolResponseFormDialog/index.tsx
+++ b/frontend/src/editor/ToolResponseFormDialog/index.tsx
@@ -4,6 +4,7 @@ import { notify } from '@inject/shared/notification/engine'
 import { memo, useCallback, useEffect, useState, type FC } from 'react'
 import useValidateExpression from '../ExpressionBuilder/useValidateExpression'
 import ToolResponseForm from '../ToolResponseFormContent'
+import { GENERIC_CONTENT } from '../assets/generalContent'
 import { addToolResponse, updateToolResponse } from '../indexeddb/operations'
 import type { ToolResponse } from '../indexeddb/types'
 
@@ -132,7 +133,7 @@ const ToolResponseFormDialog: FC<ToolResponseFormDialogProps> = ({
                 }
                 intent='primary'
                 icon='edit'
-                text='Save changes'
+                text={GENERIC_CONTENT.buttons.save}
               />
             ) : (
               <Button
@@ -150,7 +151,7 @@ const ToolResponseFormDialog: FC<ToolResponseFormDialogProps> = ({
                 }
                 intent='primary'
                 icon='plus'
-                text='Add'
+                text={GENERIC_CONTENT.buttons.add}
               />
             )
           }
diff --git a/frontend/src/editor/ToolResponses/ToolResponse.tsx b/frontend/src/editor/ToolResponses/ToolResponse.tsx
index 507f733e9fe2f9975d3a7fbc2cb0e5238481902d..344e377ea2908d86dd3c34d153abc14245784c0c 100644
--- a/frontend/src/editor/ToolResponses/ToolResponse.tsx
+++ b/frontend/src/editor/ToolResponses/ToolResponse.tsx
@@ -1,4 +1,4 @@
-import { Button, ButtonGroup, Card } from '@blueprintjs/core'
+import { Button, ButtonGroup, Card, Icon, Tooltip } from '@blueprintjs/core'
 import { notify } from '@inject/shared/notification/engine'
 import { useLiveQuery } from 'dexie-react-hooks'
 import { isEmpty } from 'lodash'
@@ -13,9 +13,10 @@ import type { LearningActivityInfo, ToolResponse } from '../indexeddb/types'
 
 interface ToolResponseProps {
   toolResponse: ToolResponse
+  isValid: boolean
 }
 
-const ToolResponseItem: FC<ToolResponseProps> = ({ toolResponse }) => {
+const ToolResponseItem: FC<ToolResponseProps> = ({ toolResponse, isValid }) => {
   const activity = useLiveQuery(
     () => getLearningActivityById(toolResponse.learningActivityId || 0),
     [toolResponse],
@@ -44,6 +45,11 @@ const ToolResponseItem: FC<ToolResponseProps> = ({ toolResponse }) => {
         {isEmpty(toolResponse.parameter)
           ? `(${activity?.name})`
           : toolResponse.parameter}
+        {!isValid && (
+          <Tooltip content='Incorrect milestone condition'>
+            <Icon icon='high-priority' intent='warning' size={20} />
+          </Tooltip>
+        )}
       </span>
       <ButtonGroup>
         <ToolResponseFormDialog
diff --git a/frontend/src/editor/ToolResponses/index.tsx b/frontend/src/editor/ToolResponses/index.tsx
index 1c96632cc93a4b63eec6a6c5fb001626bf54e23d..14a2689ac9aaf3e83989a2aaf45bb2561716108b 100644
--- a/frontend/src/editor/ToolResponses/index.tsx
+++ b/frontend/src/editor/ToolResponses/index.tsx
@@ -4,6 +4,7 @@ import { CardList } from '@blueprintjs/core'
 import { useLiveQuery } from 'dexie-react-hooks'
 import type { FC } from 'react'
 import { memo } from 'react'
+import { doesToolResponseHaveCorrectCondition } from '../indexeddb/operations'
 import ToolResponseItem from './ToolResponse'
 
 interface ToolResponsesProps {
@@ -16,11 +17,19 @@ const ToolResponses: FC<ToolResponsesProps> = ({ toolId }) => {
     [toolId],
     []
   )
+  const milestones = useLiveQuery(() => db.milestones.toArray(), [], [])
 
   return (
     <CardList>
       {toolResponses?.map((toolResponse: ToolResponse) => (
-        <ToolResponseItem key={toolResponse.id} toolResponse={toolResponse} />
+        <ToolResponseItem
+          key={toolResponse.id}
+          toolResponse={toolResponse}
+          isValid={doesToolResponseHaveCorrectCondition(
+            toolResponse,
+            milestones
+          )}
+        />
       ))}
     </CardList>
   )
diff --git a/frontend/src/editor/Tools/Tool.tsx b/frontend/src/editor/Tools/Tool.tsx
index f33e1070b19c9dff23aabde4efafeadc1dc4b152..58f0d0e5f9cb3f7e160aae7ea09de542ccbc4aab 100644
--- a/frontend/src/editor/Tools/Tool.tsx
+++ b/frontend/src/editor/Tools/Tool.tsx
@@ -1,18 +1,44 @@
-import { Button, ButtonGroup, Card } from '@blueprintjs/core'
+import { Button, ButtonGroup, Card, Icon, Tooltip } from '@blueprintjs/core'
+import { css } from '@emotion/css'
 import { notify } from '@inject/shared/notification/engine'
+import { useLiveQuery } from 'dexie-react-hooks'
 import type { FC } from 'react'
 import { memo, useCallback } from 'react'
 import ToolForm from '../ToolForm'
 import ToolResponseFormDialog from '../ToolResponseFormDialog'
 import ToolResponses from '../ToolResponses'
-import { deleteTool } from '../indexeddb/operations'
+import { deleteTool, doesToolHaveResponse } from '../indexeddb/operations'
 import type { ToolInfo } from '../indexeddb/types'
 
+const toolCard = css`
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  padding: 0rem 1rem 1rem 0;
+`
+
+const toolCardName = css`
+  height: 100%;
+  flex-grow: 1;
+`
+
+const toolResponses = css`
+  width: 100%;
+  padding-left: 2rem;
+`
+
 interface ToolProps {
   tool: ToolInfo
 }
 
 const ToolItem: FC<ToolProps> = ({ tool }) => {
+  const toolHasResponses = useLiveQuery(
+    () => doesToolHaveResponse(tool.id),
+    [tool],
+    false
+  )
+
   const handleDeleteButton = useCallback(
     async (toolInfo: ToolInfo) => {
       try {
@@ -28,16 +54,15 @@ const ToolItem: FC<ToolProps> = ({ tool }) => {
 
   return (
     <Card style={{ flexDirection: 'column' }}>
-      <div
-        style={{
-          display: 'flex',
-          justifyContent: 'space-between',
-          alignItems: 'center',
-          width: '100%',
-          padding: '0rem 1rem 1rem 0',
-        }}
-      >
-        <span style={{ height: '100%', flexGrow: 1 }}>{tool.name}</span>
+      <div className={toolCard}>
+        <span className={toolCardName}>
+          {tool.name}
+          {!toolHasResponses && (
+            <Tooltip content='Every tool has to have at least one tool response'>
+              <Icon icon='high-priority' intent='warning' size={20} />
+            </Tooltip>
+          )}
+        </span>
         <ButtonGroup>
           <ToolForm
             toolInfo={tool}
@@ -54,7 +79,7 @@ const ToolItem: FC<ToolProps> = ({ tool }) => {
           />
         </ButtonGroup>
       </div>
-      <div style={{ width: '100%', paddingLeft: '2rem' }}>
+      <div className={toolResponses}>
         <ToolResponses toolId={tool.id} />
         <ToolResponseFormDialog
           toolId={tool.id}
diff --git a/frontend/src/editor/assets/README.md b/frontend/src/editor/assets/README.md
index 69def44692c9c3b96470773bb8f1a1d9dd3affd1..b8449d5fa5d23f858f236b0c3476b1785970fca2 100644
--- a/frontend/src/editor/assets/README.md
+++ b/frontend/src/editor/assets/README.md
@@ -81,3 +81,13 @@ Each file in this directory contains checklist or form object(s) with following
 - label - required, plain string
 - tooltip - optional, plain string
 - optional - should not be changed, used as an indicator of whether the input field should be filled out
+
+### dialogContent
+- contains plaintext descriptions of dialog actions
+
+## Other
+
+### Pictures in HTML
+- the format of some values allows to use HTML string
+- if the image is being inserted, the img tag needs to specify the whole path to the picture, e.g.
+```<img src="/src/editor/assets/images/pic.jpg" alt="Picture">```
diff --git a/frontend/src/editor/assets/dialogContent.ts b/frontend/src/editor/assets/dialogContent.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6aa6752182f569d03e8822163733d099e9699d56
--- /dev/null
+++ b/frontend/src/editor/assets/dialogContent.ts
@@ -0,0 +1,10 @@
+export const DEFINITION_IMPORT_CONTENT =
+  'This will permanently delete all data in the editor. Are you sure you want to upload a new definition?'
+
+export const DATA_REMOVAL_CONTENT =
+  'This will permanently delete all data in the editor. Are you sure you want to clear the definition?'
+
+export const COMMIT_PROJECT_PANEL_CONTENT_1 =
+  'The definition changes will be commited to the project:'
+export const COMMIT_PROJECT_PANEL_CONTENT_2 =
+  'To create a new project, click on the new project option and fill in the necessary information.'
diff --git a/frontend/src/editor/assets/generalContent.tsx b/frontend/src/editor/assets/generalContent.ts
similarity index 85%
rename from frontend/src/editor/assets/generalContent.tsx
rename to frontend/src/editor/assets/generalContent.ts
index a7c91a2666c010ac3d55b066a1632967052f9179..a2008cf7690b8e70094dc92523e98b2aebcae02c 100644
--- a/frontend/src/editor/assets/generalContent.tsx
+++ b/frontend/src/editor/assets/generalContent.ts
@@ -3,6 +3,7 @@ export const GENERIC_CONTENT = {
     add: 'Add',
     edit: 'Edit',
     save: 'Save',
+    saveAndExit: 'Save & Exit',
     back: 'Back',
     next: 'Next',
     cancel: 'Cancel',
diff --git a/frontend/src/editor/assets/navigationContent.tsx b/frontend/src/editor/assets/navigationContent.ts
similarity index 100%
rename from frontend/src/editor/assets/navigationContent.tsx
rename to frontend/src/editor/assets/navigationContent.ts
diff --git a/frontend/src/editor/assets/pageContent/conclusion.tsx b/frontend/src/editor/assets/pageContent/conclusion.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/conclusion.tsx
rename to frontend/src/editor/assets/pageContent/conclusion.ts
diff --git a/frontend/src/editor/assets/pageContent/definitions.tsx b/frontend/src/editor/assets/pageContent/definitions.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/definitions.tsx
rename to frontend/src/editor/assets/pageContent/definitions.ts
diff --git a/frontend/src/editor/assets/pageContent/emails.tsx b/frontend/src/editor/assets/pageContent/emails.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/emails.tsx
rename to frontend/src/editor/assets/pageContent/emails.ts
diff --git a/frontend/src/editor/assets/pageContent/exerciseInformation.tsx b/frontend/src/editor/assets/pageContent/exerciseInformation.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/exerciseInformation.tsx
rename to frontend/src/editor/assets/pageContent/exerciseInformation.ts
diff --git a/frontend/src/editor/assets/pageContent/files.tsx b/frontend/src/editor/assets/pageContent/files.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/files.tsx
rename to frontend/src/editor/assets/pageContent/files.ts
diff --git a/frontend/src/editor/assets/pageContent/finalInformation.tsx b/frontend/src/editor/assets/pageContent/finalInformation.ts
similarity index 98%
rename from frontend/src/editor/assets/pageContent/finalInformation.tsx
rename to frontend/src/editor/assets/pageContent/finalInformation.ts
index e44e40fe47a50d2a5af882ccd89e42435cc27bcd..489a344f1b059acc17644dc8b6b28a514a11ae22 100644
--- a/frontend/src/editor/assets/pageContent/finalInformation.tsx
+++ b/frontend/src/editor/assets/pageContent/finalInformation.ts
@@ -16,6 +16,7 @@ export const FINAL_MILESTONE_FORM: Form = {
   finalMilestone: {
     label: 'Final milestone',
     tooltip: '',
+    optional: true,
   },
 }
 
diff --git a/frontend/src/editor/assets/pageContent/gitlab.tsx b/frontend/src/editor/assets/pageContent/gitlab.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/gitlab.tsx
rename to frontend/src/editor/assets/pageContent/gitlab.ts
diff --git a/frontend/src/editor/assets/pageContent/injectSpecification.tsx b/frontend/src/editor/assets/pageContent/injectSpecification.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/injectSpecification.tsx
rename to frontend/src/editor/assets/pageContent/injectSpecification.ts
diff --git a/frontend/src/editor/assets/pageContent/injects.tsx b/frontend/src/editor/assets/pageContent/injects.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/injects.tsx
rename to frontend/src/editor/assets/pageContent/injects.ts
diff --git a/frontend/src/editor/assets/pageContent/introduction.tsx b/frontend/src/editor/assets/pageContent/introduction.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/introduction.tsx
rename to frontend/src/editor/assets/pageContent/introduction.ts
diff --git a/frontend/src/editor/assets/pageContent/landingPage.tsx b/frontend/src/editor/assets/pageContent/landingPage.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/landingPage.tsx
rename to frontend/src/editor/assets/pageContent/landingPage.ts
diff --git a/frontend/src/editor/assets/pageContent/learningObjectives.tsx b/frontend/src/editor/assets/pageContent/learningObjectives.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/learningObjectives.tsx
rename to frontend/src/editor/assets/pageContent/learningObjectives.ts
diff --git a/frontend/src/editor/assets/pageContent/tools.tsx b/frontend/src/editor/assets/pageContent/tools.ts
similarity index 100%
rename from frontend/src/editor/assets/pageContent/tools.tsx
rename to frontend/src/editor/assets/pageContent/tools.ts
diff --git a/frontend/src/editor/assets/pageInformation.tsx b/frontend/src/editor/assets/pageInformation.ts
similarity index 100%
rename from frontend/src/editor/assets/pageInformation.tsx
rename to frontend/src/editor/assets/pageInformation.ts
diff --git a/frontend/src/editor/gitlabAccess.tsx b/frontend/src/editor/gitlabAccess.ts
similarity index 100%
rename from frontend/src/editor/gitlabAccess.tsx
rename to frontend/src/editor/gitlabAccess.ts
diff --git a/frontend/src/editor/indexeddb/db.tsx b/frontend/src/editor/indexeddb/db.ts
similarity index 99%
rename from frontend/src/editor/indexeddb/db.tsx
rename to frontend/src/editor/indexeddb/db.ts
index 745f012bf0704963ebe1efbbf008e90655360cc5..9201b2dd3fe570cd065f67e23b95d3b1be374a64 100644
--- a/frontend/src/editor/indexeddb/db.tsx
+++ b/frontend/src/editor/indexeddb/db.ts
@@ -44,7 +44,7 @@ db.version(dbVersion).stores({
   learningObjectives: '++id, &name',
   learningActivities: '++id, &name, description, type, learningObjectiveId',
   injectInfos: '++id, &name, description, type',
-  tools: '++id, &name, tooltipDescription, hint, defaultResponse, category',
+  tools: '++id, &name, tooltipDescription, hint, defaultResponse',
   toolResponses:
     '++id, &learningActivityId, toolId, parameter, isRegex, content, fileId',
   emailAddresses: '++id, &address, organization, description, teamVisible',
diff --git a/frontend/src/editor/indexeddb/joins.tsx b/frontend/src/editor/indexeddb/joins.ts
similarity index 96%
rename from frontend/src/editor/indexeddb/joins.tsx
rename to frontend/src/editor/indexeddb/joins.ts
index 4939599fe23f18bbb72d183eeeb79d77a0ed221f..8a0d2d0bde15bb53aed42040bdd1042876b121c0 100644
--- a/frontend/src/editor/indexeddb/joins.tsx
+++ b/frontend/src/editor/indexeddb/joins.ts
@@ -1,6 +1,6 @@
 import notEmpty from '@inject/shared/utils/notEmpty'
 import type { EditorConfig } from '../useEditorStorage'
-import { getExpression, getVariables } from '../utils'
+import { getExpression, getVariablesForOutput } from '../utils'
 import { db } from './db'
 import {
   getEmailAddressById,
@@ -47,10 +47,10 @@ export const getMilestonesWithNames = async () => {
           const response = await getToolResponseById(milestone.referenceId)
           if (!response) return
 
-          const tool = await getToolById(response?.id)
+          const tool = await getToolById(response.toolId)
           return {
             ...milestone,
-            name: `${tool ? `${tool.name} ` : ''}${response.parameter}`,
+            name: `${tool ? `${tool.name} - ` : ''}${response.parameter}`,
           }
         }
         case MilestoneEventType.EMAIL: {
@@ -60,7 +60,7 @@ export const getMilestonesWithNames = async () => {
           const address = await getEmailAddressById(template.emailAddressId)
           return {
             ...milestone,
-            name: `${address ? `${address.address} ` : ''}${template.context}`,
+            name: `${address ? `${address.address} - ` : ''}${template.context}`,
           }
         }
         default:
@@ -87,7 +87,7 @@ export const getLearningObjectivesWithActivities = async () => {
 
 const getMilestoneCondition = async (events: number[]) => {
   const milestones = await getMilestonesWithNames()
-  const variables = getVariables(milestones)
+  const variables = getVariablesForOutput(milestones)
   return getExpression(events, variables)
 }
 
diff --git a/frontend/src/editor/indexeddb/operations.tsx b/frontend/src/editor/indexeddb/operations.ts
similarity index 78%
rename from frontend/src/editor/indexeddb/operations.tsx
rename to frontend/src/editor/indexeddb/operations.ts
index e893cf1e6aa34fa93e2ca2d9a9a003b5c9e7c1ed..9b2da5e08aca94cc9c6e1e9c39412bd65df2203b 100644
--- a/frontend/src/editor/indexeddb/operations.tsx
+++ b/frontend/src/editor/indexeddb/operations.ts
@@ -1,4 +1,5 @@
 import { isEmpty } from 'lodash'
+import { validateExpression } from '../utils'
 import { db } from './db'
 import type { MarkdownContent } from './types'
 import {
@@ -125,23 +126,34 @@ export const deleteLearningActivity = async (id: number) =>
     }
   )
 
+const isToolActivitySpecified = async (activityId: number) => {
+  const response = await getToolResponseByActivityId(activityId)
+  const tool = response ? await getToolById(response?.toolId) : undefined
+  return response && tool && !isEmpty(response.parameter)
+}
+
+const isEmailActivitySpecified = async (activityId: number) => {
+  const template = await getEmailTemplateByActivityId(activityId)
+  const sender = template
+    ? await getEmailAddressById(template.emailAddressId)
+    : undefined
+  return template && sender && !isEmpty(template.context)
+}
+
+export const isActivitySpecified = async (activity: LearningActivityInfo) => {
+  switch (activity.type) {
+    case LearningActivityType.TOOL:
+      return await isToolActivitySpecified(activity.id)
+    case LearningActivityType.EMAIL:
+      return await isEmailActivitySpecified(activity.id)
+  }
+}
+
 export const areActivitiesSpecified = async () => {
   const activities = await db.learningActivities.toArray()
 
   const results = await Promise.all(
-    activities.map(async activity => {
-      if (activity.type === LearningActivityType.TOOL) {
-        const response = await getToolResponseByActivityId(activity.id)
-        const tool = response ? await getToolById(response?.toolId) : undefined
-        return response && tool && !isEmpty(response.parameter)
-      } else {
-        const template = await getEmailTemplateByActivityId(activity.id)
-        const sender = template
-          ? await getEmailAddressById(template.emailAddressId)
-          : undefined
-        return template && sender && !isEmpty(template.context)
-      }
-    })
+    activities.map(async activity => await isActivitySpecified(activity))
   )
 
   return results.every(Boolean)
@@ -188,33 +200,64 @@ export const deleteInjectInfo = async (id: number) =>
     }
   )
 
+export const isEmailInjectSpecified = async (injectId: number) => {
+  const email = await getEmailInjectByInjectInfoId(injectId)
+  const sender = email
+    ? await getEmailAddressById(email?.emailAddressId)
+    : undefined
+  return email && sender && !isEmpty(email.subject)
+}
+
+export const isQuestionnaireInjectSpecified = async (injectId: number) => {
+  const questionnaire = await getQuestionnaireByInjectInfoId(injectId)
+  const questionsCount = questionnaire
+    ? await db.questionnaireQuestions
+        .where({ questionnaireId: questionnaire?.id })
+        .count()
+    : 0
+  return questionnaire && !isEmpty(questionnaire.title) && questionsCount > 0
+}
+
+export const isInjectSpecified = async (inject: InjectInfo) => {
+  switch (inject.type) {
+    case InjectType.INFORMATION:
+      return true
+    case InjectType.EMAIL:
+      return await isEmailInjectSpecified(inject.id)
+    case InjectType.QUESTIONNAIRE:
+      return await isQuestionnaireInjectSpecified(inject.id)
+  }
+}
+
 export const areInjectsSpecified = async () => {
   const injects = await db.injectInfos.toArray()
 
   const results = await Promise.all(
-    injects.map(async inject => {
-      if (inject.type === InjectType.INFORMATION) {
-        const info = await getInformationInjectByInjectInfoId(inject.id)
-        return info
-      }
-      if (inject.type === InjectType.EMAIL) {
-        const email = await getEmailInjectByInjectInfoId(inject.id)
-        const sender = email
-          ? await getEmailAddressById(email?.emailAddressId)
-          : undefined
-        return email && sender && !isEmpty(email.subject)
-      } else {
-        const questionnaire = await getQuestionnaireByInjectInfoId(inject.id)
-        const questionsCount = questionnaire
-          ? await db.questionnaireQuestions
-              .where({ questionnaireId: questionnaire?.id })
-              .count()
-          : 0
-        return (
-          questionnaire && !isEmpty(questionnaire.title) && questionsCount > 0
-        )
-      }
-    })
+    injects.map(async inject => await isInjectSpecified(inject))
+  )
+
+  return results.every(Boolean)
+}
+
+export const doesInjectHaveCorrectCondition = async (
+  injectId: number,
+  milestones: Milestone[]
+) => {
+  const control = await getInjectControlByInjectInfoId(injectId)
+
+  return validateExpression(control?.milestoneCondition || [], milestones)
+    .isValid
+}
+
+export const doInjectsHaveCorrectConditions = async () => {
+  const injects = await db.injectInfos.toArray()
+  const milestones = await db.milestones.toArray()
+
+  const results = await Promise.all(
+    injects.map(
+      async inject =>
+        await doesInjectHaveCorrectCondition(inject.id, milestones)
+    )
   )
 
   return results.every(Boolean)
@@ -277,15 +320,56 @@ export const deleteToolResponse = async (id: number) =>
       .delete()
   })
 
+export const doesToolHaveResponse = async (toolId: number) =>
+  (await db.toolResponses.where({ toolId }).count()) > 0
+
 export const doToolsHaveResponses = async () => {
   const tools = await db.tools.toArray()
 
   const results = await Promise.all(
-    tools.map(
-      async tool =>
-        (await db.toolResponses.where({ toolId: tool.id }).count()) > 0
-    )
+    tools.map(async tool => await doesToolHaveResponse(tool.id))
+  )
+  return results.every(Boolean)
+}
+
+export const doesToolResponseHaveCorrectCondition = (
+  response: ToolResponse,
+  milestones: Milestone[]
+) => validateExpression(response.milestoneCondition || [], milestones).isValid
+
+export const doToolResponsesHaveCorrectConditions = async () => {
+  const responses = await db.toolResponses.toArray()
+  const milestones = await db.milestones.toArray()
+
+  const results = responses.map(response =>
+    doesToolResponseHaveCorrectCondition(response, milestones)
+  )
+
+  return results.every(Boolean)
+}
+
+export const doesLearningActivityHaveCorrectCondition = async (
+  activity: LearningActivityInfo,
+  milestones: Milestone[]
+) => {
+  if (activity.type !== LearningActivityType.TOOL) return true
+
+  const response = await getToolResponseByActivityId(activity.id)
+
+  return validateExpression(response?.milestoneCondition || [], milestones)
+    .isValid
+}
+
+export const doLearningActivitiesHaveCorrectConditions = async () => {
+  const responses = await db.toolResponses.toArray()
+  const milestones = await db.milestones.toArray()
+
+  const results = responses.map(
+    response =>
+      !response.learningActivityId ||
+      doesToolResponseHaveCorrectCondition(response, milestones)
   )
+
   return results.every(Boolean)
 }
 
diff --git a/frontend/src/editor/indexeddb/types.tsx b/frontend/src/editor/indexeddb/types.ts
similarity index 99%
rename from frontend/src/editor/indexeddb/types.tsx
rename to frontend/src/editor/indexeddb/types.ts
index d14f9bcfea8d3012565f8c23dfe56219803c6676..eebbaa4136b47cd533cb1d4f48312f8491b88bc9 100644
--- a/frontend/src/editor/indexeddb/types.tsx
+++ b/frontend/src/editor/indexeddb/types.ts
@@ -41,7 +41,6 @@ export type InjectInfo = {
 export type ToolInfo = {
   id: number
   name: string
-  category?: string
   tooltipDescription?: string
   defaultResponse: string
   hint?: string
diff --git a/frontend/src/editor/types.tsx b/frontend/src/editor/types.ts
similarity index 100%
rename from frontend/src/editor/types.tsx
rename to frontend/src/editor/types.ts
diff --git a/frontend/src/editor/utils.tsx b/frontend/src/editor/utils.ts
similarity index 62%
rename from frontend/src/editor/utils.tsx
rename to frontend/src/editor/utils.ts
index 40d47494d036987952ed37f3d08d38732a3d8535..4e756f01a6a682b3604a8d94c236c96719709a9a 100644
--- a/frontend/src/editor/utils.tsx
+++ b/frontend/src/editor/utils.ts
@@ -1,9 +1,9 @@
 import type { IconName, OptionProps } from '@blueprintjs/core'
 import notEmpty from '@inject/shared/utils/notEmpty'
-import { isEqual } from 'lodash'
 import type {
   InjectInfo,
   LearningActivityInfo,
+  Milestone,
   MilestoneName,
 } from './indexeddb/types'
 import {
@@ -41,12 +41,12 @@ export const getInjectIcon = (inject: InjectInfo): IconName => {
 }
 
 // expression builder
-export const DEFAULT_OPTION: OptionProps = { value: '0', label: '+' }
-export const OPENING_BRACKET: OptionProps = { value: '-1', label: '(' }
-export const CLOSING_BRACKET: OptionProps = { value: '-2', label: ')' }
-export const NOT: OptionProps = { value: '-3', label: 'not' }
-export const AND: OptionProps = { value: '-4', label: 'and' }
-export const OR: OptionProps = { value: '-5', label: 'or' }
+export const DEFAULT_OPTION: OptionProps = { value: 0, label: '+' }
+export const OPENING_BRACKET: OptionProps = { value: -1, label: '(' }
+export const CLOSING_BRACKET: OptionProps = { value: -2, label: ')' }
+export const NOT: OptionProps = { value: -3, label: 'not' }
+export const AND: OptionProps = { value: -4, label: 'and' }
+export const OR: OptionProps = { value: -5, label: 'or' }
 export const OPERATORS: OptionProps[] = [AND, OR]
 export const ALL_OPERATORS: OptionProps[] = [
   CLOSING_BRACKET,
@@ -56,18 +56,24 @@ export const ALL_OPERATORS: OptionProps[] = [
 ]
 
 export const getBlockFromId = (id: number, variables: OptionProps[]) =>
-  ALL_OPERATORS.find(operator => Number(operator.value) === id) ||
-  variables.find(variable => Number(variable.value) === id)
+  ALL_OPERATORS.find(operator => operator.value === id) ||
+  variables.find(variable => variable.value === id)
 
 export const getExpression = (expression: number[], variables: OptionProps[]) =>
   expression.map(id => getBlockFromId(id, variables)).filter(notEmpty)
 
 export const getVariables = (milestones: MilestoneName[]) =>
   milestones.map(milestone => ({
-    value: milestone.id.toString(),
+    value: milestone.id,
     label: getMilestoneName(milestone),
   }))
 
+export const getVariablesForOutput = (milestones: MilestoneName[]) =>
+  milestones.map(milestone => ({
+    value: milestone.id,
+    label: getMilestoneNameForOutput(milestone),
+  }))
+
 export const getPrefixByMilestoneType = (type: MilestoneEventType) => {
   switch (type) {
     case MilestoneEventType.LEARNING_ACTIVITY:
@@ -84,7 +90,10 @@ export const getPrefixByMilestoneType = (type: MilestoneEventType) => {
 }
 
 export const getMilestoneName = (milestone: MilestoneName) =>
-  `${getPrefixByMilestoneType(milestone.type)}_${milestone.name.replace(/\s+/g, '_')}${milestone.type === MilestoneEventType.TOOL || milestone.type === MilestoneEventType.EMAIL ? `_${milestone.id}` : ''}`
+  `${getPrefixByMilestoneType(milestone.type)}: ${milestone.name}`
+
+export const getMilestoneNameForOutput = (milestone: MilestoneName) =>
+  `${getPrefixByMilestoneType(milestone.type)}_${milestone.name.replace(/\s+/g, '_').replace(/[^A-Za-z0-9_]/g, '')}_${milestone.id}`
 
 type ValidationResult = {
   isValid: boolean
@@ -92,78 +101,82 @@ type ValidationResult = {
 }
 
 export const validateExpression = (
-  expression: OptionProps[],
-  variables: OptionProps[]
+  expression: number[],
+  variables: Milestone[]
 ): ValidationResult => {
   let balance = 0
-  let lastBlock: OptionProps | undefined = undefined
+  let lastBlock: number | undefined = undefined
 
   for (const block of expression) {
-    if (isEqual(block, OPENING_BRACKET)) {
+    if (block === OPENING_BRACKET.value) {
       balance++
-      if (variables.find(value => isEqual(value, lastBlock))) {
+      if (variables.find(value => value.id === lastBlock)) {
         return { isValid: false, error: 'Missing operator after variable' }
       }
-      if (isEqual(lastBlock, CLOSING_BRACKET)) {
+      if (lastBlock === CLOSING_BRACKET.value) {
         return {
           isValid: false,
           error: 'Missing operator after closing bracket',
         }
       }
-    } else if (isEqual(block, CLOSING_BRACKET)) {
+    } else if (block === CLOSING_BRACKET.value) {
       balance--
       if (balance < 0) {
         return { isValid: false, error: 'More closing brackets' }
       }
-      if (isEqual(lastBlock, OPENING_BRACKET)) {
+      if (lastBlock === OPENING_BRACKET.value) {
         return { isValid: false, error: 'Empty brackets' }
       }
       if (
-        OPERATORS.find(value => isEqual(value, lastBlock)) ||
-        isEqual(lastBlock, NOT)
+        OPERATORS.find(operator => operator.value === lastBlock) ||
+        lastBlock === NOT.value
       ) {
         return { isValid: false, error: 'Missing variable after operator' }
       }
-    } else if (OPERATORS.find(value => isEqual(value, block))) {
-      if (OPERATORS.find(value => isEqual(value, lastBlock))) {
+    } else if (OPERATORS.find(operator => operator.value === block)) {
+      if (!lastBlock)
+        return { isValid: false, error: 'Cannot start with an operator' }
+      if (OPERATORS.find(operator => operator.value === lastBlock)) {
         return { isValid: false, error: 'Two operators in a row' }
       }
-      if (isEqual(lastBlock, NOT)) {
+      if (lastBlock === NOT.value) {
         return { isValid: false, error: 'Missing variable after NOT' }
       }
-      if (isEqual(lastBlock, OPENING_BRACKET)) {
+      if (lastBlock === OPENING_BRACKET.value) {
         return {
           isValid: false,
           error: 'Missing variable after opening bracket',
         }
       }
-    } else if (variables.find(value => isEqual(value, block))) {
-      if (variables.find(value => isEqual(value, lastBlock))) {
+    } else if (variables.find(value => value.id === block)) {
+      if (variables.find(value => value.id === lastBlock)) {
         return { isValid: false, error: 'Two variables in a row' }
       }
-      if (isEqual(lastBlock, CLOSING_BRACKET)) {
+      if (lastBlock === CLOSING_BRACKET.value) {
         return {
           isValid: false,
           error: 'Missing operator after closing bracket',
         }
       }
-    } else if (isEqual(block, NOT)) {
-      if (isEqual(lastBlock, CLOSING_BRACKET)) {
+    } else if (block === NOT.value) {
+      if (lastBlock === CLOSING_BRACKET.value) {
         return {
           isValid: false,
           error: 'Missing operator after closing bracket',
         }
       }
-      if (variables.find(value => isEqual(value, lastBlock))) {
+      if (variables.find(value => value.id === lastBlock)) {
         return { isValid: false, error: 'Missing operator after variable' }
       }
+    } else {
+      return { isValid: false, error: 'Missing variable' }
     }
     lastBlock = block
   }
 
   if (
-    OPERATORS.find(value => isEqual(value, lastBlock)) ||
-    isEqual(lastBlock, NOT)
+    OPERATORS.find(operator => operator.value === lastBlock) ||
+    lastBlock === NOT.value
   ) {
     return { isValid: false, error: 'Cannot end with operator' }
   } else if (balance > 0) {
diff --git a/frontend/src/editor/yaml/generate/channels.tsx b/frontend/src/editor/yaml/generate/channels.ts
similarity index 100%
rename from frontend/src/editor/yaml/generate/channels.tsx
rename to frontend/src/editor/yaml/generate/channels.ts
diff --git a/frontend/src/editor/yaml/generate/config.tsx b/frontend/src/editor/yaml/generate/config.ts
similarity index 100%
rename from frontend/src/editor/yaml/generate/config.tsx
rename to frontend/src/editor/yaml/generate/config.ts
diff --git a/frontend/src/editor/yaml/generate/email.tsx b/frontend/src/editor/yaml/generate/email.ts
similarity index 100%
rename from frontend/src/editor/yaml/generate/email.tsx
rename to frontend/src/editor/yaml/generate/email.ts
diff --git a/frontend/src/editor/yaml/generate/injects.tsx b/frontend/src/editor/yaml/generate/injects.ts
similarity index 100%
rename from frontend/src/editor/yaml/generate/injects.tsx
rename to frontend/src/editor/yaml/generate/injects.ts
diff --git a/frontend/src/editor/yaml/generate/milestones.tsx b/frontend/src/editor/yaml/generate/milestones.ts
similarity index 85%
rename from frontend/src/editor/yaml/generate/milestones.tsx
rename to frontend/src/editor/yaml/generate/milestones.ts
index 3de321393d6fa70bc08415a88254c90568805cf7..d377c2117eeb45554056f7999450030ec6758539 100644
--- a/frontend/src/editor/yaml/generate/milestones.tsx
+++ b/frontend/src/editor/yaml/generate/milestones.ts
@@ -2,7 +2,7 @@ import { getUsedMilestones } from '@/editor/indexeddb/joins'
 import type { MilestoneName } from '@/editor/indexeddb/types'
 import { MilestoneEventType } from '@/editor/indexeddb/types'
 import type { EditorConfig } from '@/editor/useEditorStorage'
-import { getMilestoneName } from '@/editor/utils'
+import { getMilestoneNameForOutput } from '@/editor/utils'
 import { pickBy } from 'lodash'
 
 export const generateMilestones = async (config: EditorConfig) => {
@@ -10,7 +10,7 @@ export const generateMilestones = async (config: EditorConfig) => {
 
   return milestones.map((milestone: MilestoneName) =>
     pickBy({
-      name: getMilestoneName(milestone),
+      name: getMilestoneNameForOutput(milestone),
       activity:
         milestone.type === MilestoneEventType.LEARNING_ACTIVITY
           ? milestone.name
diff --git a/frontend/src/editor/yaml/generate/objectives.tsx b/frontend/src/editor/yaml/generate/objectives.ts
similarity index 100%
rename from frontend/src/editor/yaml/generate/objectives.tsx
rename to frontend/src/editor/yaml/generate/objectives.ts
diff --git a/frontend/src/editor/yaml/generate/questionnaires.tsx b/frontend/src/editor/yaml/generate/questionnaires.ts
similarity index 100%
rename from frontend/src/editor/yaml/generate/questionnaires.tsx
rename to frontend/src/editor/yaml/generate/questionnaires.ts
diff --git a/frontend/src/editor/yaml/generate/shared.tsx b/frontend/src/editor/yaml/generate/shared.ts
similarity index 91%
rename from frontend/src/editor/yaml/generate/shared.tsx
rename to frontend/src/editor/yaml/generate/shared.ts
index 819eb38f39681c7c69a4b05d89601de4975fab76..705efce6436eac749f1af8cf9f7d395c8f00bf09 100644
--- a/frontend/src/editor/yaml/generate/shared.tsx
+++ b/frontend/src/editor/yaml/generate/shared.ts
@@ -4,7 +4,7 @@ import type {
   MilestoneName,
   Overlay,
 } from '@/editor/indexeddb/types'
-import { getMilestoneName } from '@/editor/utils'
+import { getMilestoneNameForOutput } from '@/editor/utils'
 import type { OptionProps } from '@blueprintjs/core'
 import { isEmpty, pickBy } from 'lodash'
 
@@ -29,7 +29,7 @@ export const generateControl = (
 ) => {
   const generatedControl = pickBy({
     activate_milestone: activateMilestone
-      ? getMilestoneName(activateMilestone)
+      ? getMilestoneNameForOutput(activateMilestone)
       : '',
     milestone_condition: milestoneCondition
       ?.map(block => block.label)
diff --git a/frontend/src/editor/yaml/generate/tools.tsx b/frontend/src/editor/yaml/generate/tools.ts
similarity index 100%
rename from frontend/src/editor/yaml/generate/tools.tsx
rename to frontend/src/editor/yaml/generate/tools.ts
diff --git a/frontend/src/editor/yaml/parse/channels.tsx b/frontend/src/editor/yaml/parse/channels.ts
similarity index 100%
rename from frontend/src/editor/yaml/parse/channels.tsx
rename to frontend/src/editor/yaml/parse/channels.ts
diff --git a/frontend/src/editor/yaml/parse/config.tsx b/frontend/src/editor/yaml/parse/config.ts
similarity index 100%
rename from frontend/src/editor/yaml/parse/config.tsx
rename to frontend/src/editor/yaml/parse/config.ts
diff --git a/frontend/src/editor/yaml/parse/email.tsx b/frontend/src/editor/yaml/parse/email.ts
similarity index 100%
rename from frontend/src/editor/yaml/parse/email.tsx
rename to frontend/src/editor/yaml/parse/email.ts
diff --git a/frontend/src/editor/yaml/parse/injects.tsx b/frontend/src/editor/yaml/parse/injects.ts
similarity index 100%
rename from frontend/src/editor/yaml/parse/injects.tsx
rename to frontend/src/editor/yaml/parse/injects.ts
diff --git a/frontend/src/editor/yaml/parse/milestones.tsx b/frontend/src/editor/yaml/parse/milestones.ts
similarity index 100%
rename from frontend/src/editor/yaml/parse/milestones.tsx
rename to frontend/src/editor/yaml/parse/milestones.ts
diff --git a/frontend/src/editor/yaml/parse/objectives.tsx b/frontend/src/editor/yaml/parse/objectives.ts
similarity index 100%
rename from frontend/src/editor/yaml/parse/objectives.tsx
rename to frontend/src/editor/yaml/parse/objectives.ts
diff --git a/frontend/src/editor/yaml/parse/questionnaires.tsx b/frontend/src/editor/yaml/parse/questionnaires.ts
similarity index 100%
rename from frontend/src/editor/yaml/parse/questionnaires.tsx
rename to frontend/src/editor/yaml/parse/questionnaires.ts
diff --git a/frontend/src/editor/yaml/parse/shared.tsx b/frontend/src/editor/yaml/parse/shared.ts
similarity index 94%
rename from frontend/src/editor/yaml/parse/shared.tsx
rename to frontend/src/editor/yaml/parse/shared.ts
index 0ea75bf953335b59aa0dab4ea244c7a9d8da2f11..55d40dfcab98c2f223f9b972eda45370c8210d62 100644
--- a/frontend/src/editor/yaml/parse/shared.tsx
+++ b/frontend/src/editor/yaml/parse/shared.ts
@@ -33,8 +33,8 @@ export const getMilestoneCondition = (
   milestonesWithIds: MappedMilestone[]
 ) =>
   condition
-    .split(' ')
-    .map(
+    .match(/\bnot\b|\band\b|\bor\b|[a-zA-Z0-9_]+|[()]/g)
+    ?.map(
       value =>
         Number(
           ALL_OPERATORS.find(operator => operator.label === value)?.value
diff --git a/frontend/src/editor/yaml/parse/tools.tsx b/frontend/src/editor/yaml/parse/tools.ts
similarity index 100%
rename from frontend/src/editor/yaml/parse/tools.tsx
rename to frontend/src/editor/yaml/parse/tools.ts
diff --git a/frontend/src/editor/yaml/types.tsx b/frontend/src/editor/yaml/types.ts
similarity index 100%
rename from frontend/src/editor/yaml/types.tsx
rename to frontend/src/editor/yaml/types.ts
diff --git a/frontend/src/editor/zip/createDefinitionZip.tsx b/frontend/src/editor/zip/createDefinitionZip.ts
similarity index 100%
rename from frontend/src/editor/zip/createDefinitionZip.tsx
rename to frontend/src/editor/zip/createDefinitionZip.ts
diff --git a/frontend/src/editor/zip/loadDefinitionZip.tsx b/frontend/src/editor/zip/loadDefinitionZip.ts
similarity index 100%
rename from frontend/src/editor/zip/loadDefinitionZip.tsx
rename to frontend/src/editor/zip/loadDefinitionZip.ts
diff --git a/frontend/src/editor/zip/utils.tsx b/frontend/src/editor/zip/utils.ts
similarity index 100%
rename from frontend/src/editor/zip/utils.tsx
rename to frontend/src/editor/zip/utils.ts
diff --git a/frontend/src/logic/StaffSelector/index.tsx b/frontend/src/logic/StaffSelector/index.tsx
index 3f25d22669ce891c3de52140afe55abbb591a6e8..3358f816c4d6aefa849a63fd1429c90d16463677 100644
--- a/frontend/src/logic/StaffSelector/index.tsx
+++ b/frontend/src/logic/StaffSelector/index.tsx
@@ -42,6 +42,10 @@ const StaffSelector: FC<StaffSelectorInterface> = ({ enableRefresh }) => {
           link={['/analyst']}
           button={{ icon: 'series-search', text: 'Analyst' }}
         />
+        <LinkButton
+          link={['/editor']}
+          button={{ icon: 'annotation', text: 'Designer' }}
+        />
       </ButtonGroup>
       {enableRefresh && <Reloader minimal onRefetch={refetch} />}
     </div>
diff --git a/frontend/src/pages/editor/_layout.tsx b/frontend/src/pages/editor/_layout.tsx
index 45e2df52a81bd91fb998fe4064412d2e907e94ca..ded2c09c1166f580d26afd1dec3be650944ea95f 100644
--- a/frontend/src/pages/editor/_layout.tsx
+++ b/frontend/src/pages/editor/_layout.tsx
@@ -1,7 +1,8 @@
-import { useSetPageTitle } from '@/utils'
+import { useActiveBoundary, useSetPageTitle } from '@/utils'
 import { Outlet } from 'react-router-dom'
 
 const Layout = () => {
+  useActiveBoundary()
   useSetPageTitle('Editor')
 
   return <Outlet />
diff --git a/frontend/src/pages/editor/create/other.tsx b/frontend/src/pages/editor/create/other.tsx
index b171943363d50ed982033ecc775186d70593d61e..e8c647db09c7e344f8b2722c9f19574373a9f32b 100644
--- a/frontend/src/pages/editor/create/other.tsx
+++ b/frontend/src/pages/editor/create/other.tsx
@@ -1,14 +1,47 @@
 import EditorPage from '@/editor/EditorPage'
+import OverviewCard from '@/editor/OverviewCard'
+import {
+  doToolResponsesHaveCorrectConditions,
+  doToolsHaveResponses,
+} from '@/editor/indexeddb/operations'
 import { PageNames } from '@/editor/types'
 import useEditorAccessStorage from '@/editor/useEditorAccessStorage'
 import { useNavigate } from '@/router'
 import { Card, CardList } from '@blueprintjs/core'
-import { memo } from 'react'
+import { useLiveQuery } from 'dexie-react-hooks'
+import { memo, useMemo } from 'react'
 
 const OtherPage = () => {
   const nav = useNavigate()
   const [access] = useEditorAccessStorage()
 
+  const toolsHaveResponses = useLiveQuery(
+    () => doToolsHaveResponses(),
+    [],
+    false
+  )
+  const toolsHaveCorrectConditions = useLiveQuery(
+    () => doToolResponsesHaveCorrectConditions(),
+    [],
+    false
+  )
+
+  const areSpecified = useMemo(
+    () => toolsHaveResponses && toolsHaveCorrectConditions,
+    [toolsHaveResponses, toolsHaveCorrectConditions]
+  )
+  const tooltipContent = useMemo(
+    () =>
+      [
+        !toolsHaveResponses &&
+          'Every tool has to have at least one tool response',
+        !toolsHaveCorrectConditions && 'Incorrect milestone conditions',
+      ]
+        .filter(Boolean)
+        .join(', '),
+    [toolsHaveResponses, toolsHaveCorrectConditions]
+  )
+
   return (
     <EditorPage
       pageKey={PageNames.OTHER}
@@ -18,9 +51,12 @@ const OtherPage = () => {
       nextDisabled={!access?.specificationsFilled}
     >
       <CardList>
-        <Card interactive onClick={() => nav(`/editor/create/tools`)}>
-          Tools
-        </Card>
+        <OverviewCard
+          name='Tools'
+          onClick={() => nav(`/editor/create/tools`)}
+          isSpecified={areSpecified}
+          tooltipContent={tooltipContent}
+        />
         <Card interactive onClick={() => nav(`/editor/create/emails`)}>
           Email addresses
         </Card>
diff --git a/frontend/src/views/EditorView/index.tsx b/frontend/src/views/EditorView/index.tsx
index a48ddec32eb74fe820107f1780e569f36833711c..a27955326cdafcee74c4b4fdbcd2e4d566426e66 100644
--- a/frontend/src/views/EditorView/index.tsx
+++ b/frontend/src/views/EditorView/index.tsx
@@ -8,8 +8,9 @@ import { db } from '@/editor/indexeddb/db'
 import {
   areActivitiesSpecified,
   areInjectsSpecified,
+  doInjectsHaveCorrectConditions,
+  doToolResponsesHaveCorrectConditions,
   doToolsHaveResponses,
-  getMilestoneById,
 } from '@/editor/indexeddb/operations'
 import useEditorAccessStorage from '@/editor/useEditorAccessStorage'
 import useEditorStorage from '@/editor/useEditorStorage'
@@ -40,15 +41,20 @@ const EditorView: FC<PropsWithChildren> = ({ children }) => {
     false
   )
   const injectsSpecified = useLiveQuery(() => areInjectsSpecified(), [], false)
+  const injectsHaveCorrectConditions = useLiveQuery(
+    () => doInjectsHaveCorrectConditions(),
+    [],
+    false
+  )
   const toolsHaveResponses = useLiveQuery(
     () => doToolsHaveResponses(),
     [],
     false
   )
-  const finalMilestoneSpecified = useLiveQuery(
-    () => getMilestoneById(config?.finalMilestone || 0),
-    [config?.finalMilestone],
-    undefined
+  const toolsHaveCorrectConditions = useLiveQuery(
+    () => doToolResponsesHaveCorrectConditions(),
+    [],
+    false
   )
 
   const [allIntroChecked, setAllIntroChecked] = useState(
@@ -92,12 +98,12 @@ const EditorView: FC<PropsWithChildren> = ({ children }) => {
       specificationsFilled:
         activitiesSpecified &&
         injectsSpecified &&
+        injectsHaveCorrectConditions &&
         toolsHaveResponses &&
+        toolsHaveCorrectConditions &&
         prev?.injectsFilled,
       finalInformationFilled:
-        (config?.exerciseDuration || 0) > 0 &&
-        finalMilestoneSpecified &&
-        prev?.specificationsFilled,
+        (config?.exerciseDuration || 0) > 0 && prev?.specificationsFilled,
       conclusionFilled: allConclusionChecked && prev?.finalInformationFilled,
     }))
   }, [
@@ -114,7 +120,8 @@ const EditorView: FC<PropsWithChildren> = ({ children }) => {
     activitiesSpecified,
     toolsHaveResponses,
     injectsSpecified,
-    finalMilestoneSpecified,
+    injectsHaveCorrectConditions,
+    toolsHaveCorrectConditions,
     config?.exerciseDuration,
     allConclusionChecked,
   ])
@@ -168,7 +175,12 @@ const EditorView: FC<PropsWithChildren> = ({ children }) => {
         node: !hide && <Navbar />,
       },
     ],
-    [hide]
+    [
+      hide,
+      access?.exerciseInformationFilled,
+      config?.name,
+      gitlabConfig?.project,
+    ]
   )
 
   return (