Repository: uzh-bf/klicker-uzh
Pull request #4704: feat: add possibility to define case study items inline in element edit form
Author: login: sjschlapbach
Labels: name: feature
name: size:XL
description: This PR changes 500-999 lines, ignoring generated files.
Created: 2025-05-21T16:04:21Z | Merged: 2025-05-21T20:03:24Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added the ability to manually create and manage case study items when creating or duplicating a case study question, alongside selecting from existing collections.
  - Introduced a new interface component for manual entry of case study items, with localized guidance, tooltips, and toggling between manual and existing item selection modes.
- **Improvements**
  - Enhanced validation schema and error handling for case study items, including conditional requirements based on item selection mode and new error messages for missing manually defined items.
  - Improved localization with updated English and German strings supporting manual case study item entry workflows.
  - Refined error handling in element submission forms to explicitly show failure toasts on submission errors.
  - Simplified mutation error handling by returning explicit success/failure booleans.
  - Expanded effect dependencies to ensure proper form validation and state updates.
- **Bug Fixes**
  - Fixed form validation and error display for both manually created and selected case study items.
- **Style**
  - Minor styling adjustments for improved layout in selection components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/frontend-manage/src/components/activities/templates/TemplateNewElementModal.tsx b/apps/frontend-manage/src/components/activities/templates/TemplateNewElementModal.tsx
index c91d43cd55..11c2cf5640 100644
--- a/apps/frontend-manage/src/components/activities/templates/TemplateNewElementModal.tsx
+++ b/apps/frontend-manage/src/components/activities/templates/TemplateNewElementModal.tsx
@@ -1,5 +1,4 @@
 import { ElementStatus } from '@klicker-uzh/graphql/dist/ops'
-import { useState } from 'react'
 import ElementEditForm from '../../questions/manipulation/ElementEditForm'
 import { ElementEditMode } from '../../questions/manipulation/ElementEditModal'
 import { ElementFormTypes } from '../../questions/manipulation/types'
@@ -19,8 +18,6 @@ function TemplateNewElementModal({
   templateElement: ActivityTemplateElementFormValues
   onSaveNewElement: (formValues: ElementFormTypes) => void
 }) {
-  const [failureToast, setFailureToast] = useState(false)
-
   const instanceFormValues = useFormValuesFromElementInstance({
     instance: templateElement.instance,
   })
@@ -42,10 +39,9 @@ function TemplateNewElementModal({
       initialStatus={ElementStatus.Ready}
       onSubmitElement={async (values) => {
         onSaveNewElement(values)
+        return true
       }}
       setAutoSavedElement={() => {}} // auto-save is disabled for templates for the moment
-      failureToast={failureToast}
-      setFailureToast={setFailureToast}
       updateInstances={false}
       setUpdateInstances={() => {}} // instance updates are only available for existing elements
       includeTemplateUpdates={false}
diff --git a/apps/frontend-manage/src/components/questions/manipulation/ElementEditForm.tsx b/apps/frontend-manage/src/components/questions/manipulation/ElementEditForm.tsx
index d150b073b2..e99dcf8cd7 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/ElementEditForm.tsx
+++ b/apps/frontend-manage/src/components/questions/manipulation/ElementEditForm.tsx
@@ -51,8 +51,6 @@ function ElementEditForm({
   initialStatus,
   onSubmitElement,
   setAutoSavedElement,
-  failureToast,
-  setFailureToast,
   updateInstances,
   setUpdateInstances,
   includeTemplateUpdates,
@@ -77,11 +75,8 @@ function ElementEditForm({
   initialStatus: ElementStatus
   onSubmitElement: (
     values: ElementFormTypes & { status: ElementStatus }
-  ) => Promise<void>
+  ) => Promise<boolean>
   setAutoSavedElement: Dispatch<SetStateAction<ElementFormTypes>>
-  // failure handling
-  failureToast: boolean
-  setFailureToast: Dispatch<SetStateAction<boolean>>
   // instance update controls
   updateInstances: boolean
   setUpdateInstances: Dispatch<SetStateAction<boolean>>
@@ -91,6 +86,7 @@ function ElementEditForm({
   const t = useTranslations()
 
   const [activeTab, setActiveTab] = useState('preview')
+  const [failureToast, setFailureToast] = useState(false)
   const [elementStatus, setElementStatus] = useState(initialStatus)
   const [answerCollectionEntries, setAnswerCollectionEntries] = useState<
     { id: number; value: string }[]
@@ -111,11 +107,19 @@ function ElementEditForm({
       validationSchema={questionManipulationSchema}
       onSubmit={async (values, { setSubmitting }) => {
         setSubmitting(true)
-        await onSubmitElement({ ...values, status: elementStatus })
+        const success = await onSubmitElement({
+          ...values,
+          status: elementStatus,
+        })
 
         // close modal, set success toast
         setSubmitting(false)
-        onSuccess()
+        if (!success) {
+          setFailureToast(true)
+          setFailureToast(true)
+        } else {
+          onSuccess()
+        }
       }}
     >
       {({
@@ -267,6 +271,10 @@ function ElementEditForm({
 
                   {values.type === ElementType.CaseStudy && (
                     <CaseStudyOptions
+                      creationMode={
+                        mode === ElementEditMode.CREATE ||
+                        mode === ElementEditMode.DUPLICATE
+                      }
                       templateId={templateId}
                       isTemplate={isTemplate}
                       inputsDisabled={inputsDisabled}
diff --git a/apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx b/apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx
index 9ba23b58af..b0487cbe3f 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx
+++ b/apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx
@@ -1,7 +1,9 @@
 import { useMutation, useQuery } from '@apollo/client'
 import {
+  CreateAnswerCollectionDocument,
   ElementStatus,
   ElementType,
+  GetAnswerCollectionsInfoDocument,
   GetSingleQuestionDocument,
   GetUserElementsDocument,
   GetUserTagsDocument,
@@ -27,7 +29,11 @@ import {
   prepareNumericalArgs,
   prepareSelectionArgs,
 } from './helpers'
-import { ElementFormTypes } from './types'
+import {
+  ElementFormTypes,
+  ElementFormTypesCaseStudy,
+  ElementFormTypesCaseStudySolutions,
+} from './types'
 import useElementFormInitialValues from './useElementFormInitialValues'
 
 export enum ElementEditMode {
@@ -58,7 +64,6 @@ function ElementEditModal({
   const isDuplication = mode === ElementEditMode.DUPLICATE
   const [updateInstances, setUpdateInstances] = useState(true)
   const [includeTemplateUpdates, setIncludeTemplateUpdates] = useState(false)
-  const [failureToast, setFailureToast] = useState(false)
 
   const [autoSavedElement, setAutoSavedElement] =
     useLocalStorage<ElementFormTypes>(
@@ -98,6 +103,7 @@ function ElementEditModal({
   const [manipulateCaseStudyQuestion] = useMutation(
     ManipulateCaseStudyQuestionDocument
   )
+  const [createAnswerCollection] = useMutation(CreateAnswerCollectionDocument)
   const [updateElementInstances] = useMutation(UpdateElementInstancesDocument)
 
   const initialValues = useElementFormInitialValues({
@@ -130,8 +136,6 @@ function ElementEditModal({
       initialStatus={dataQuestion?.question?.status ?? ElementStatus.Ready}
       open={isOpen}
       onClose={() => handleSetIsOpen(false)}
-      failureToast={failureToast}
-      setFailureToast={setFailureToast}
       updateInstances={updateInstances}
       setUpdateInstances={setUpdateInstances}
       includeTemplateUpdates={includeTemplateUpdates}
@@ -155,8 +159,7 @@ function ElementEditModal({
 
             const data = result.data?.manipulateContentElement
             if (data?.__typename !== 'ContentElement' || !data.id) {
-              setFailureToast(true)
-              return
+              return false
             }
 
             break
@@ -179,8 +182,7 @@ function ElementEditModal({
 
             const data = result.data?.manipulateFlashcardElement
             if (data?.__typename !== 'FlashcardElement' || !data.id) {
-              setFailureToast(true)
-              return
+              return false
             }
 
             break
@@ -205,8 +207,7 @@ function ElementEditModal({
 
             const data = result.data?.manipulateChoicesQuestion
             if (data?.__typename !== 'ChoicesElement' || !data.id) {
-              setFailureToast(true)
-              return
+              return false
             }
 
             break
@@ -229,8 +230,7 @@ function ElementEditModal({
 
             const data = result.data?.manipulateNumericalQuestion
             if (data?.__typename !== 'NumericalElement' || !data.id) {
-              setFailureToast(true)
-              return
+              return false
             }
 
             break
@@ -253,8 +253,7 @@ function ElementEditModal({
 
             const data = result.data?.manipulateFreeTextQuestion
             if (data?.__typename !== 'FreeTextElement' || !data.id) {
-              setFailureToast(true)
-              return
+              return false
             }
 
             break
@@ -277,18 +276,115 @@ function ElementEditModal({
 
             const data = result.data?.manipulateSelectionQuestion
             if (data?.__typename !== 'SelectionElement' || !data.id) {
-              setFailureToast(true)
-              return
+              return false
             }
 
             break
           }
 
           case ElementType.CaseStudy: {
+            // make a copy of the form values (passed by reference) to optionally update them in case of an inline answer collection definition
+            const innerValues: ElementFormTypesCaseStudy & {
+              status: ElementStatus
+            } = JSON.parse(JSON.stringify(values))
+
+            // if the items for the case study question were defined inline, create a new answer collection from them
+            if (values.options.itemSelectionMode === 'new') {
+              if (!values.options.manuallyCreatedItems) {
+                return false
+              }
+
+              const { data } = await createAnswerCollection({
+                variables: {
+                  name: `AC Case Study ${values.name}`,
+                  description: `Answer collection containing all the items used in the context of the case study ${values.name}`,
+                  answers:
+                    values.options.manuallyCreatedItems.map(
+                      (item) => item.value
+                    ) ?? [],
+                },
+                update: (cache, { data }) => {
+                  if (!data?.createAnswerCollection) return
+
+                  const queryData = cache.readQuery({
+                    query: GetAnswerCollectionsInfoDocument,
+                  })
+                  const previousCollections =
+                    queryData?.getAnswerCollectionsInfo
+                  if (!previousCollections) return
+
+                  cache.writeQuery({
+                    query: GetAnswerCollectionsInfoDocument,
+                    data: {
+                      getAnswerCollectionsInfo: [
+                        ...previousCollections,
+                        data.createAnswerCollection,
+                      ],
+                    },
+                  })
+                },
+              })
+
+              if (!data?.createAnswerCollection) {
+                return false
+              }
+
+              // set the answer collection id to the newly created answer collection
+              innerValues.options.answerCollection = String(
+                data.createAnswerCollection.id
+              )
+
+              // set the items to the newly created answer collection items (in the same order as the values were defined)
+              const entries = data.createAnswerCollection.entries ?? []
+              const entryIds = values.options.manuallyCreatedItems.flatMap(
+                (createdItem) => {
+                  const entry = entries.find(
+                    (entry) => entry.value === createdItem.value
+                  )
+                  return entry ? entry.id : []
+                }
+              )
+              innerValues.options.selectedItems = entryIds
+
+              if (values.options.hasSampleSolution) {
+                // create a map between the old item index and the new correct answer collection entry ids
+                const itemOldIdNewIdMap = new Map<number, number>()
+                values.options.manuallyCreatedItems.forEach((createdItem) => {
+                  const entry = entries.find(
+                    (entry) => entry.value === createdItem.value
+                  )
+                  if (entry) {
+                    itemOldIdNewIdMap.set(createdItem.id, entry.id)
+                  }
+                })
+
+                // update the ids of the criterion solutions for all cases
+                innerValues.options.cases = values.options.cases.map((c) => {
+                  const mappedSolutions: ElementFormTypesCaseStudySolutions =
+                    Object.fromEntries(
+                      Object.entries(c.solutions ?? {}).flatMap(
+                        ([key, value]) => {
+                          const oldId = parseInt(key.split('-')[1])
+                          const newItemId = itemOldIdNewIdMap.get(oldId)
+
+                          if (typeof newItemId === 'undefined') {
+                            return []
+                          }
+
+                          return [[`itemId-${newItemId}`, value]]
+                        }
+                      )
+                    )
+
+                  return { ...c, solutions: mappedSolutions }
+                })
+              }
+            }
+
             const args = prepareCaseStudyArgs({
               elementId,
               isDuplication,
-              values,
+              values: innerValues,
             })
 
             const result = await manipulateCaseStudyQuestion({
@@ -301,8 +397,7 @@ function ElementEditModal({
 
             const data = result.data?.manipulateCaseStudyQuestion
             if (data?.__typename !== 'CaseStudyElement' || !data.id) {
-              setFailureToast(true)
-              return
+              return false
             }
 
             break
@@ -322,6 +417,8 @@ function ElementEditModal({
             })
           }
         }
+
+        return true
       }}
       onSuccess={() => {
         // remove local storage entry
diff --git a/apps/frontend-manage/src/components/questions/manipulation/ElementFormErrors.tsx b/apps/frontend-manage/src/components/questions/manipulation/ElementFormErrors.tsx
index 92db153a80..cbefe85808 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/ElementFormErrors.tsx
+++ b/apps/frontend-manage/src/components/questions/manipulation/ElementFormErrors.tsx
@@ -212,6 +212,13 @@ function ElementFormErrors({
             <li>{`${t('manage.elements.selectedItems')}: ${errors.options.selectedItems}`}</li>
           )}
 
+        {'options' in errors &&
+          errors.options &&
+          'manuallyCreatedItems' in errors.options &&
+          errors.options.manuallyCreatedItems && (
+            <li>{`${t('manage.elements.definedItems')}: ${errors.options.manuallyCreatedItems}`}</li>
+          )}
+
         {'options' in errors &&
           errors.options &&
           'criteria' in errors.options &&
diff --git a/apps/frontend-manage/src/components/questions/manipulation/ElementTypeMonitor.tsx b/apps/frontend-manage/src/components/questions/manipulation/ElementTypeMonitor.tsx
index 3dc6a56f35..30d1bcdd14 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/ElementTypeMonitor.tsx
+++ b/apps/frontend-manage/src/components/questions/manipulation/ElementTypeMonitor.tsx
@@ -36,7 +36,7 @@ function ElementTypeMonitor({
     }
 
     validateForm()
-  }, [elementType])
+  }, [elementType, setElementDataTypename, validateForm])
 
   return null
 }
diff --git a/apps/frontend-manage/src/components/questions/manipulation/helpers.ts b/apps/frontend-manage/src/components/questions/manipulation/helpers.ts
index 5d40135e9f..a6a8cf7e89 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/helpers.ts
+++ b/apps/frontend-manage/src/components/questions/manipulation/helpers.ts
@@ -262,9 +262,8 @@ export function prepareCaseStudyArgs({
 
     options: {
       hasSampleSolution: values.options.hasSampleSolution,
-      answerCollection: parseInt(values.options.answerCollection),
+      answerCollection: parseInt(values.options.answerCollection!),
       collectionItemIds: values.options.selectedItems,
-
       criteria: values.options.criteria.map((criterion, index) => ({
         id: criterion.id,
         name: criterion.name,
diff --git a/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx b/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx
index 702f55fa3b..a5d67c4c93 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx
+++ b/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx
@@ -15,22 +15,28 @@ import useSelectedAnswerEntry from './useSelectedAnswerEntry'
 function CaseStudyCollectionSelection({
   loading,
   disabled,
+  creationMode,
   isTemplate,
   collections,
   setSelectedItems,
   hasSampleSolution,
+  itemSelectionMode,
   setAnswerCollectionEntries,
+  setItemSelectionMode,
   refetchAnswerCollections,
 }: {
   loading: boolean
   disabled: boolean
+  creationMode: boolean
   isTemplate: boolean
   collections: Pick<AnswerCollection, 'id' | 'name' | 'entries'>[]
   setSelectedItems: Dispatch<SetStateAction<{ id: number; name: string }[]>>
   hasSampleSolution: boolean
+  itemSelectionMode: 'existing' | 'new'
   setAnswerCollectionEntries: Dispatch<
     SetStateAction<{ id: number; value: string }[]>
   >
+  setItemSelectionMode: (newValue: 'existing' | 'new') => void
   refetchAnswerCollections: () => Promise<any>
 }) {
   const t = useTranslations()
@@ -57,6 +63,7 @@ function CaseStudyCollectionSelection({
   const selectedAnswers = useSelectedAnswerEntry({
     field: itemsField,
     collectionAnswers,
+    itemSelectionMode,
     setSelectedItems,
   })
 
@@ -69,7 +76,7 @@ function CaseStudyCollectionSelection({
 
   return (
     <>
-      <div className="flex flex-row items-end gap-2">
+      <div className="-mb-3 flex flex-row items-end gap-2">
         <SelectField
           required
           disabled={disabled || loading}
@@ -98,9 +105,7 @@ function CaseStudyCollectionSelection({
             },
           }))}
           data={{ cy: 'select-answer-collection' }}
-          className={{
-            select: { trigger: 'h-9 w-80' },
-          }}
+          className={{ select: { trigger: 'h-9 w-80' } }}
         />
         <Button
           disabled={disabled || loading}
@@ -115,6 +120,24 @@ function CaseStudyCollectionSelection({
           />
         </Button>
       </div>
+      {creationMode && (
+        <Button
+          basic
+          onClick={() => {
+            // reset the selected items
+            setAnswerCollectionEntries([])
+            setSelectedItems([])
+
+            // switch to the creation mode for new answer collection options
+            setItemSelectionMode('new')
+          }}
+          className={{
+            root: 'text-primary-100 hover:text-primary-100 w-max px-0.5 py-1 text-sm hover:bg-transparent hover:underline',
+          }}
+        >
+          {t('manage.elements.enterItemsManually')}
+        </Button>
+      )}
       <div>
         <FormLabel
           required
diff --git a/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCriterionModeMonitor.tsx b/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCriterionModeMonitor.tsx
index 3570fdcd4c..a8d5cfdbe6 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCriterionModeMonitor.tsx
+++ b/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCriterionModeMonitor.tsx
@@ -13,7 +13,7 @@ function CaseStudyCriterionModeMonitor({
   criteriaField: FieldInputProps<ElementFormTypesCaseStudyCriterion[]>
   criteriaHelpers: FieldHelperProps<ElementFormTypesCaseStudyCriterion[]>
 }) {
-  // if the mode of a criterio is unset, default to "range"
+  // if the mode of a criterion is unset, default to "range"
   useEffect(() => {
     if (typeof criterion.mode === 'undefined') {
       const newCriteria: ElementFormTypesCaseStudyCriterion[] =
diff --git a/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx b/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
new file mode 100644
index 0000000000..eb08419fa1
--- /dev/null
+++ b/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
@@ -0,0 +1,166 @@
+import { FormLabel, UserNotification } from '@uzh-bf/design-system'
+import { useField } from 'formik'
+import { useTranslations } from 'next-intl'
+import { Dispatch, SetStateAction } from 'react'
+import Creatable from 'react-select/creatable'
+import { ElementFormTypesCaseStudy } from '../types'
+import useSelectedNewItems from './useSelectedNewItems'
+
+function CaseStudyManualItemCreation({
+  disabled,
+  itemSelectionMode,
+  setItemSelectionMode,
+  setAnswerCollectionEntries,
+  setSelectedItems,
+}: {
+  disabled: boolean
+  itemSelectionMode: 'existing' | 'new'
+  setItemSelectionMode: (newValue: 'existing' | 'new') => void
+  setAnswerCollectionEntries: Dispatch<
+    SetStateAction<{ id: number; value: string }[]>
+  >
+  setSelectedItems: Dispatch<SetStateAction<{ id: number; name: string }[]>>
+}) {
+  const t = useTranslations()
+  const [manualItemsField, ____, manualItemsHelpers] = useField<
+    ElementFormTypesCaseStudy['options']['manuallyCreatedItems']
+  >('options.manuallyCreatedItems')
+  const [casesField, ___, casesHelpers] =
+    useField<ElementFormTypesCaseStudy['options']['cases']>('options.cases')
+
+  // update the selected items state whenever the manually created items change
+  useSelectedNewItems({
+    createdItems: manualItemsField.value ?? [],
+    itemSelectionMode,
+    setSelectedItems,
+  })
+
+  return (
+    <div>
+      <UserNotification type="info">
+        {t.rich('manage.elements.enterItemsManuallyExplanation', {
+          b: (text) => <b>{text}</b>,
+          button: (text) => (
+            <span
+              onClick={() => {
+                // reset the selected items
+                setAnswerCollectionEntries([])
+
+                // switch to the selection mode for existing answer collections
+                setItemSelectionMode('existing')
+              }}
+              className="cursor-pointer hover:underline"
+            >
+              {text}
+            </span>
+          ),
+        })}
+      </UserNotification>
+
+      <FormLabel
+        required
+        label={t('shared.generic.caseStudyItems')}
+        tooltip={t('manage.elements.newCaseStudyItemsTooltip')}
+        labelType="small"
+        className={{ label: 'mt-2' }}
+      />
+      <Creatable
+        isClearable
+        isMulti
+        isDisabled={disabled}
+        value={
+          manualItemsField.value?.map((item) => ({
+            label: item.value,
+            value: item.id,
+          })) ?? []
+        }
+        options={
+          manualItemsField.value?.map((item) => ({
+            label: item.value,
+            value: item.id,
+          })) ?? []
+        }
+        classNames={{ container: () => 'w-full h-9' }}
+        onChange={(newValue) => {
+          // set the new collection items
+          const prevItems = manualItemsField.value ?? []
+          const newItems = newValue.map((item) => ({
+            id: item.value,
+            value: item.label.trim(),
+          }))
+          manualItemsHelpers.setValue(newItems)
+
+          // check if an item has been removed and conditionally remove the solutions for this item
+          if (newItems.length < prevItems.length) {
+            // identify the removed item
+            const removedItem = prevItems.find(
+              (prevItem) =>
+                !newItems.map((item) => item.id).includes(prevItem.id)
+            )
+
+            // if an item has been removed, remove the corresponding key from the case solutions
+            if (removedItem) {
+              const newCases = casesField.value?.map((caseItem) => {
+                // if no solutions are set, skip this case
+                if (!('solutions' in caseItem) || !caseItem.solutions) {
+                  return caseItem
+                }
+
+                // filter out all solution entries for the removed item
+                const newSolutions = Object.fromEntries(
+                  Object.entries(caseItem.solutions).filter(
+                    ([itemIdString]) =>
+                      itemIdString !== `itemId-${removedItem.id}`
+                  )
+                )
+
+                return {
+                  ...caseItem,
+                  solutions: newSolutions,
+                }
+              })
+
+              // update the cases field
+              casesHelpers.setValue(newCases)
+            }
+          }
+
+          // update the answer collection state for correct validation
+          setAnswerCollectionEntries(
+            newItems.map((item) => ({
+              id: item.id,
+              value: item.value,
+            }))
+          )
+        }}
+        onCreateOption={(newValue) => {
+          // add the new tag to the list of new collection items, if it does not exist already
+          if (
+            !manualItemsField.value?.some(
+              (existingItem) => existingItem.value === newValue.trim()
+            ) &&
+            newValue.trim() !== ''
+          ) {
+            manualItemsHelpers.setValue([
+              ...(manualItemsField.value ?? []),
+              {
+                id: Math.floor(Math.random() * 1000000 + 1),
+                value: newValue.trim(),
+              },
+            ])
+
+            // update the answer collection state for correct validation
+            setAnswerCollectionEntries((prev) => [
+              ...prev,
+              { id: prev.length, value: newValue.trim() },
+            ])
+          }
+        }}
+        placeholder={t('manage.elements.selectCaseStudyItems')}
+        noOptionsMessage={() => t('manage.elements.noMatchingOptionFound')}
+      />
+    </div>
+  )
+}
+
+export default CaseStudyManualItemCreation
diff --git a/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx b/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx
index bc863e79f2..2985443070 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx
+++ b/apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx
@@ -2,6 +2,7 @@ import { useQuery } from '@apollo/client'
 import { GetAnswerCollectionsElementsDocument } from '@klicker-uzh/graphql/dist/ops'
 import Loader from '@klicker-uzh/shared-components/src/Loader'
 import { UserNotification } from '@uzh-bf/design-system'
+import { useField } from 'formik'
 import { useTranslations } from 'next-intl'
 import Link from 'next/link'
 import { Dispatch, SetStateAction, useState } from 'react'
@@ -10,8 +11,10 @@ import CaseStudyCasesFields, {
 } from './CaseStudyCasesFields'
 import CaseStudyCollectionSelection from './CaseStudyCollectionSelection'
 import CaseStudyCriteriaFields from './CaseStudyCriteriaFields'
+import CaseStudyManualItemCreation from './CaseStudyManualItemCreation'
 
 interface CaseStudyOptionsProps extends CaseStudySetterProps {
+  creationMode: boolean
   templateId?: string
   isTemplate: boolean
   hasSampleSolution: boolean
@@ -21,6 +24,7 @@ interface CaseStudyOptionsProps extends CaseStudySetterProps {
 }
 
 function CaseStudyOptions({
+  creationMode,
   templateId,
   isTemplate,
   inputsDisabled = false,
@@ -30,6 +34,10 @@ function CaseStudyOptions({
   setAnswerCollectionEntries,
 }: CaseStudyOptionsProps) {
   const t = useTranslations()
+  const [selectionMode, _, selectionModeHelpers] = useField<'existing' | 'new'>(
+    'options.itemSelectionMode'
+  )
+
   const [selectedItems, setSelectedItems] = useState<
     { id: number; name: string }[]
   >([])
@@ -66,18 +74,37 @@ function CaseStudyOptions({
 
   return (
     <div className="flex flex-col gap-4">
-      <CaseStudyCollectionSelection
-        loading={loading}
-        disabled={inputsDisabled}
-        isTemplate={isTemplate}
-        collections={collections}
-        setSelectedItems={setSelectedItems}
-        hasSampleSolution={hasSampleSolution}
-        setAnswerCollectionEntries={setAnswerCollectionEntries}
-        refetchAnswerCollections={async () =>
-          await refetchAnswerCollections({ templateId })
-        }
-      />
+      {(selectionMode.value === 'existing' ||
+        typeof selectionMode.value === 'undefined') && (
+        <CaseStudyCollectionSelection
+          loading={loading}
+          disabled={inputsDisabled}
+          creationMode={creationMode}
+          isTemplate={isTemplate}
+          collections={collections}
+          setSelectedItems={setSelectedItems}
+          hasSampleSolution={hasSampleSolution}
+          itemSelectionMode={selectionMode.value}
+          setAnswerCollectionEntries={setAnswerCollectionEntries}
+          setItemSelectionMode={(newValue) =>
+            selectionModeHelpers.setValue(newValue)
+          }
+          refetchAnswerCollections={async () =>
+            await refetchAnswerCollections({ templateId })
+          }
+        />
+      )}
+      {creationMode && selectionMode.value === 'new' && (
+        <CaseStudyManualItemCreation
+          disabled={inputsDisabled}
+          itemSelectionMode={selectionMode.value}
+          setItemSelectionMode={(newValue) =>
+            selectionModeHelpers.setValue(newValue)
+          }
+          setAnswerCollectionEntries={setAnswerCollectionEntries}
+          setSelectedItems={setSelectedItems}
+        />
+      )}
       <hr className="border-uzh-grey-40 my-2 w-full border-2" />
       <CaseStudyCriteriaFields disabled={inputsDisabled} />
       <hr className="border-uzh-grey-40 my-2 w-full border-2" />
diff --git a/apps/frontend-manage/src/components/questions/manipulation/options/SelectionOptions.tsx b/apps/frontend-manage/src/components/questions/manipulation/options/SelectionOptions.tsx
index b8a3aa5e49..90fccb3b02 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/options/SelectionOptions.tsx
+++ b/apps/frontend-manage/src/components/questions/manipulation/options/SelectionOptions.tsx
@@ -57,6 +57,7 @@ function SelectionOptions({
 
   // filter the available answer options for the ones included in the current form state
   const selectedAnswers = useSelectedAnswerEntry({
+    itemSelectionMode: 'existing',
     field,
     collectionAnswers,
   })
diff --git a/apps/frontend-manage/src/components/questions/manipulation/options/useSelectedAnswerEntry.ts b/apps/frontend-manage/src/components/questions/manipulation/options/useSelectedAnswerEntry.ts
index 8de183f43b..eb530e88ba 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/options/useSelectedAnswerEntry.ts
+++ b/apps/frontend-manage/src/components/questions/manipulation/options/useSelectedAnswerEntry.ts
@@ -4,14 +4,16 @@ import { Dispatch, SetStateAction, useMemo } from 'react'
 function useSelectedAnswerEntry({
   field,
   collectionAnswers,
+  itemSelectionMode,
   setSelectedItems,
 }: {
   field: FieldInputProps<number[]>
   collectionAnswers: { label: string; value: number }[]
+  itemSelectionMode: 'existing' | 'new'
   setSelectedItems?: Dispatch<SetStateAction<{ id: number; name: string }[]>>
 }) {
   return useMemo(() => {
-    if (!field.value) {
+    if (!field.value || itemSelectionMode === 'new') {
       return []
     }
 
@@ -26,7 +28,7 @@ function useSelectedAnswerEntry({
     }
 
     return selectedAnswers
-  }, [collectionAnswers, field.value, setSelectedItems])
+  }, [collectionAnswers, field.value, itemSelectionMode, setSelectedItems])
 }
 
 export default useSelectedAnswerEntry
diff --git a/apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts b/apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts
new file mode 100644
index 0000000000..feacc41cf8
--- /dev/null
+++ b/apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts
@@ -0,0 +1,30 @@
+import { Dispatch, SetStateAction, useMemo } from 'react'
+import { ElementFormTypesCaseStudy } from '../types'
+
+function useSelectedNewItems({
+  createdItems,
+  itemSelectionMode,
+  setSelectedItems,
+}: {
+  createdItems: ElementFormTypesCaseStudy['options']['manuallyCreatedItems']
+  itemSelectionMode: 'existing' | 'new'
+  setSelectedItems?: Dispatch<SetStateAction<{ id: number; name: string }[]>>
+}) {
+  return useMemo(() => {
+    if (
+      typeof createdItems === 'undefined' ||
+      createdItems.length === 0 ||
+      typeof itemSelectionMode === 'undefined' ||
+      itemSelectionMode === 'existing'
+    ) {
+      return []
+    }
+
+    setSelectedItems?.(
+      createdItems.map((item) => ({ id: item.id, name: item.value }))
+    )
+    return null
+  }, [createdItems, itemSelectionMode, setSelectedItems])
+}
+
+export default useSelectedNewItems
diff --git a/apps/frontend-manage/src/components/questions/manipulation/types.ts b/apps/frontend-manage/src/components/questions/manipulation/types.ts
index 9970ff94be..882ff9ef21 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/types.ts
+++ b/apps/frontend-manage/src/components/questions/manipulation/types.ts
@@ -73,11 +73,11 @@ export interface ElementFormTypesSelection extends SharedQuestionFormProps {
 
 // key of top level record is `itemId-${item.id}`, key of nested record is criterion id
 export type ElementFormTypesCaseStudySolution = Record<
-  string,
+  string, // criterion id
   { min: string; max: string }
 >
 export type ElementFormTypesCaseStudySolutions = Record<
-  string,
+  string, // `itemId-${item.id}`
   ElementFormTypesCaseStudySolution
 >
 
@@ -100,9 +100,11 @@ export interface ElementFormTypesCaseStudy extends SharedQuestionFormProps {
   type: ElementType.CaseStudy
   explanation?: string | null
   options: {
+    itemSelectionMode?: 'existing' | 'new'
     hasSampleSolution: boolean
-    answerCollection: string
-    selectedItems: number[] // items that should be evaluated with respect to the defined criteria
+    answerCollection?: string
+    selectedItems?: number[] // from AC: items that should be evaluated with respect to the defined criteria
+    manuallyCreatedItems?: { id: number; value: string }[] // new implicit AC: items that should be evaluated with respect to the defined criteria
     cases: {
       id: string // short id
       title: string
diff --git a/apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts b/apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts
index 009b2266c7..7aefce8f7c 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts
+++ b/apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts
@@ -103,12 +103,14 @@ function useArtificialElementInstance({
                     ? { entries: answerCollectionEntries }
                     : undefined,
                 items:
+                  (!('itemSelectionMode' in values.options) ||
+                    values.options.itemSelectionMode === 'existing') &&
                   typeof answerCollectionEntries !== 'undefined' &&
                   answerCollectionEntries.length > 0
                     ? answerCollectionEntries.flatMap((entry) => {
                         if (
                           'selectedItems' in values.options &&
-                          values.options.selectedItems.includes(entry.id)
+                          values.options.selectedItems?.includes(entry.id)
                         ) {
                           return {
                             id: entry.id,
@@ -118,7 +120,9 @@ function useArtificialElementInstance({
 
                         return []
                       })
-                    : [],
+                    : 'manuallyCreatedItems' in values.options
+                      ? (values.options.manuallyCreatedItems ?? [])
+                      : [],
                 criteria:
                   'criteria' in values.options && values.options.criteria
                     ? values.options.criteria.map((criterion, criterionIx) => ({
diff --git a/apps/frontend-manage/src/components/questions/manipulation/useElementFormInitialValues.ts b/apps/frontend-manage/src/components/questions/manipulation/useElementFormInitialValues.ts
index 0e5ffe9192..f707e2d761 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/useElementFormInitialValues.ts
+++ b/apps/frontend-manage/src/components/questions/manipulation/useElementFormInitialValues.ts
@@ -150,10 +150,12 @@ function useElementFormInitialValues({
         type: ElementType.CaseStudy,
         options: {
           hasSampleSolution: options.hasSampleSolution ?? false,
+          itemSelectionMode: 'existing', // manual definition of elements not supported for element editing
           answerCollection: options.answerCollectionId
             ? String(options.answerCollectionId)
             : '',
           selectedItems: options.collectionItemIds ?? [],
+          manuallyCreatedItems: [],
           criteria:
             options.criteria?.map((criterion) => ({
               ...criterion,
diff --git a/apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts b/apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts
index 837b4b8f95..fb92ff8fa8 100644
--- a/apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts
+++ b/apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts
@@ -449,14 +449,40 @@ function useOptionsSchemaCaseStudy() {
 
   return {
     hasSampleSolution: yup.boolean(),
-    answerCollection: yup
-      .string()
-      .required(t('manage.formErrors.CSAnswerCollectionRequired')),
+    itemSelectionMode: yup.string().oneOf(['existing', 'new']),
+    answerCollection: yup.string().when('itemSelectionMode', {
+      is: (value?: 'existing' | 'new') => !value || value === 'existing',
+      then: (schema) =>
+        schema.required(t('manage.formErrors.CSAnswerCollectionRequired')),
+      otherwise: (schema) => schema,
+    }),
     selectedItems: yup
       .array()
       .of(yup.number())
-      .required(t('manage.formErrors.CSItemsRequired'))
-      .min(1, t('manage.formErrors.CSItemsRequired')),
+      .when('itemSelectionMode', {
+        is: (value?: 'existing' | 'new') => !value || value === 'existing',
+        then: (schema) =>
+          schema
+            .required(t('manage.formErrors.CSItemsRequired'))
+            .min(1, t('manage.formErrors.CSItemsRequired')),
+        otherwise: (schema) => schema,
+      }),
+    manuallyCreatedItems: yup
+      .array()
+      .of(
+        yup.object().shape({
+          id: yup.number().required(),
+          value: yup.string().required(),
+        })
+      )
+      .when('itemSelectionMode', {
+        is: (value?: 'existing' | 'new') => value === 'new',
+        then: (schema) =>
+          schema
+            .required(t('manage.formErrors.CSNewItemsRequired'))
+            .min(1, t('manage.formErrors.CSNewItemsRequired')),
+        otherwise: (schema) => schema,
+      }),
     criteria: yup
       .array()
       .of(
@@ -588,13 +614,23 @@ function useOptionsSchemaCaseStudy() {
                 })
               }
 
+              const itemMode: ElementFormTypesCaseStudy['options']['itemSelectionMode'] =
+                grandparent.itemSelectionMode
               const selectedItems: ElementFormTypesCaseStudy['options']['selectedItems'] =
                 grandparent.selectedItems
+              const createdItems: ElementFormTypesCaseStudy['options']['manuallyCreatedItems'] =
+                grandparent.manuallyCreatedItems
               const criteria: ElementFormTypesCaseStudy['options']['criteria'] =
                 grandparent.criteria
 
+              // set the answer collection items according to the mode setting
+              const items =
+                typeof itemMode === 'undefined' || itemMode === 'existing'
+                  ? (selectedItems ?? [])
+                  : (createdItems?.map((item) => item.id) ?? [])
+
               const solutionKeys = Object.keys(solutions)
-              if (solutionKeys.length !== selectedItems.length) {
+              if (solutionKeys.length !== items.length) {
                 return this.createError({
                   message: t(
                     'manage.formErrors.CSSolutionsMissingCertainItems'
@@ -602,8 +638,8 @@ function useOptionsSchemaCaseStudy() {
                 })
               }
 
-              for (let itemIx = 0; itemIx < selectedItems.length; itemIx++) {
-                const itemId = selectedItems[itemIx]
+              for (let itemIx = 0; itemIx < items.length; itemIx++) {
+                const itemId = items[itemIx]
                 const criterionSolutions = solutions[`itemId-${itemId}`]
 
                 if (
diff --git a/apps/frontend-manage/src/pages/index.tsx b/apps/frontend-manage/src/pages/index.tsx
index e27651e6e6..4d7ddfb96d 100644
--- a/apps/frontend-manage/src/pages/index.tsx
+++ b/apps/frontend-manage/src/pages/index.tsx
@@ -340,7 +340,6 @@ function Index() {
                     icon={faMagnifyingGlass}
                     className={{
                       input: 'h-10 pl-9',
-
                       field: 'w-30 rounded-md pr-3',
                     }}
                   />
diff --git a/packages/graphql/src/graphql/ops/MCreateAnswerCollection.graphql b/packages/graphql/src/graphql/ops/MCreateAnswerCollection.graphql
index 8f30dbc187..cefea950cc 100644
--- a/packages/graphql/src/graphql/ops/MCreateAnswerCollection.graphql
+++ b/packages/graphql/src/graphql/ops/MCreateAnswerCollection.graphql
@@ -24,5 +24,10 @@ mutation CreateAnswerCollection(
     isDeletable
     isRemovable
     sharingType
+
+    entries {
+      id
+      value
+    }
   }
 }
diff --git a/packages/graphql/src/ops.ts b/packages/graphql/src/ops.ts
index 13429afaed..31e496b05f 100644
--- a/packages/graphql/src/ops.ts
+++ b/packages/graphql/src/ops.ts
@@ -4386,7 +4386,7 @@ export type CreateAnswerCollectionMutationVariables = Exact<{
 }>;
 
 
-export type CreateAnswerCollectionMutation = { __typename?: 'Mutation', createAnswerCollection?: { __typename?: 'AnswerCollection', id: number, name: string, description: string, ownerShortname?: string | null, numSharedUsers?: number | null, numOfEntries?: number | null, permissionLevel?: PermissionLevel | null, isOwner?: boolean | null, isManager?: boolean | null, isEditor?: boolean | null, isImported?: boolean | null, isShared?: boolean | null, isDeletable?: boolean | null, isRemovable?: boolean | null, sharingType?: SharingType | null } | null };
+export type CreateAnswerCollectionMutation = { __typename?: 'Mutation', createAnswerCollection?: { __typename?: 'AnswerCollection', id: number, name: string, description: string, ownerShortname?: string | null, numSharedUsers?: number | null, numOfEntries?: number | null, permissionLevel?: PermissionLevel | null, isOwner?: boolean | null, isManager?: boolean | null, isEditor?: boolean | null, isImported?: boolean | null, isShared?: boolean | null, isDeletable?: boolean | null, isRemovable?: boolean | null, sharingType?: SharingType | null, entries?: Array<{ __typename?: 'AnswerCollectionEntry', id: number, value: string }> | null } | null };
 
 export type CreateCatalogCollectionMutationVariables = Exact<{
   name: Scalars['String']['input'];
@@ -6283,7 +6283,7 @@ export const ChangeParticipantLocaleDocument = {"kind":"Document","definitions":
 export const ChangeShortnameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ChangeShortname"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"shortname"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"changeShortname"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"shortname"},"value":{"kind":"Variable","name":{"kind":"Name","value":"shortname"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shortname"}}]}}]}}]} as unknown as DocumentNode<ChangeShortnameMutation, ChangeShortnameMutationVariables>;
 export const ChangeUserGroupNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ChangeUserGroupName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"changeUserGroupName"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}}]}]}}]} as unknown as DocumentNode<ChangeUserGroupNameMutation, ChangeUserGroupNameMutationVariables>;
 export const ChangeUserLocaleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ChangeUserLocale"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"locale"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"LocaleType"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"changeUserLocale"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"locale"},"value":{"kind":"Variable","name":{"kind":"Name","value":"locale"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"locale"}}]}}]}}]} as unknown as DocumentNode<ChangeUserLocaleMutation, ChangeUserLocaleMutationVariables>;
-export const CreateAnswerCollectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateAnswerCollection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"description"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"answers"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createAnswerCollection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"description"},"value":{"kind":"Variable","name":{"kind":"Name","value":"description"}}},{"kind":"Argument","name":{"kind":"Name","value":"answers"},"value":{"kind":"Variable","name":{"kind":"Name","value":"answers"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"ownerShortname"}},{"kind":"Field","name":{"kind":"Name","value":"numSharedUsers"}},{"kind":"Field","name":{"kind":"Name","value":"numOfEntries"}},{"kind":"Field","name":{"kind":"Name","value":"permissionLevel"}},{"kind":"Field","name":{"kind":"Name","value":"isOwner"}},{"kind":"Field","name":{"kind":"Name","value":"isManager"}},{"kind":"Field","name":{"kind":"Name","value":"isEditor"}},{"kind":"Field","name":{"kind":"Name","value":"isImported"}},{"kind":"Field","name":{"kind":"Name","value":"isShared"}},{"kind":"Field","name":{"kind":"Name","value":"isDeletable"}},{"kind":"Field","name":{"kind":"Name","value":"isRemovable"}},{"kind":"Field","name":{"kind":"Name","value":"sharingType"}}]}}]}}]} as unknown as DocumentNode<CreateAnswerCollectionMutation, CreateAnswerCollectionMutationVariables>;
+export const CreateAnswerCollectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateAnswerCollection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"description"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"answers"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createAnswerCollection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"description"},"value":{"kind":"Variable","name":{"kind":"Name","value":"description"}}},{"kind":"Argument","name":{"kind":"Name","value":"answers"},"value":{"kind":"Variable","name":{"kind":"Name","value":"answers"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"ownerShortname"}},{"kind":"Field","name":{"kind":"Name","value":"numSharedUsers"}},{"kind":"Field","name":{"kind":"Name","value":"numOfEntries"}},{"kind":"Field","name":{"kind":"Name","value":"permissionLevel"}},{"kind":"Field","name":{"kind":"Name","value":"isOwner"}},{"kind":"Field","name":{"kind":"Name","value":"isManager"}},{"kind":"Field","name":{"kind":"Name","value":"isEditor"}},{"kind":"Field","name":{"kind":"Name","value":"isImported"}},{"kind":"Field","name":{"kind":"Name","value":"isShared"}},{"kind":"Field","name":{"kind":"Name","value":"isDeletable"}},{"kind":"Field","name":{"kind":"Name","value":"isRemovable"}},{"kind":"Field","name":{"kind":"Name","value":"sharingType"}},{"kind":"Field","name":{"kind":"Name","value":"entries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]}}]} as unknown as DocumentNode<CreateAnswerCollectionMutation, CreateAnswerCollectionMutationVariables>;
 export const CreateCatalogCollectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateCatalogCollection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"access"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ObjectAccess"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createCatalogCollection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"access"},"value":{"kind":"Variable","name":{"kind":"Name","value":"access"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"access"}},{"kind":"Field","name":{"kind":"Name","value":"ownerShortname"}},{"kind":"Field","name":{"kind":"Name","value":"isOwner"}},{"kind":"Field","name":{"kind":"Name","value":"isManager"}},{"kind":"Field","name":{"kind":"Name","value":"isRequested"}},{"kind":"Field","name":{"kind":"Name","value":"isShared"}}]}}]}}]} as unknown as DocumentNode<CreateCatalogCollectionMutation, CreateCatalogCollectionMutationVariables>;
 export const CreateCourseDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateCourse"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"displayName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"description"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"color"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"startDate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Date"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"endDate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Date"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isGroupCreationEnabled"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"groupDeadlineDate"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Date"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"maxGroupSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"preferredGroupSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"notificationEmail"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isGamificationEnabled"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createCourse"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"displayName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"displayName"}}},{"kind":"Argument","name":{"kind":"Name","value":"description"},"value":{"kind":"Variable","name":{"kind":"Name","value":"description"}}},{"kind":"Argument","name":{"kind":"Name","value":"color"},"value":{"kind":"Variable","name":{"kind":"Name","value":"color"}}},{"kind":"Argument","name":{"kind":"Name","value":"startDate"},"value":{"kind":"Variable","name":{"kind":"Name","value":"startDate"}}},{"kind":"Argument","name":{"kind":"Name","value":"endDate"},"value":{"kind":"Variable","name":{"kind":"Name","value":"endDate"}}},{"kind":"Argument","name":{"kind":"Name","value":"isGroupCreationEnabled"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isGroupCreationEnabled"}}},{"kind":"Argument","name":{"kind":"Name","value":"groupDeadlineDate"},"value":{"kind":"Variable","name":{"kind":"Name","value":"groupDeadlineDate"}}},{"kind":"Argument","name":{"kind":"Name","value":"maxGroupSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"maxGroupSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"preferredGroupSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"preferredGroupSize"}}},{"kind":"Argument","name":{"kind":"Name","value":"notificationEmail"},"value":{"kind":"Variable","name":{"kind":"Name","value":"notificationEmail"}}},{"kind":"Argument","name":{"kind":"Name","value":"isGamificationEnabled"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isGamificationEnabled"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"startDate"}},{"kind":"Field","name":{"kind":"Name","value":"endDate"}},{"kind":"Field","name":{"kind":"Name","value":"isGroupCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"groupDeadlineDate"}},{"kind":"Field","name":{"kind":"Name","value":"maxGroupSize"}},{"kind":"Field","name":{"kind":"Name","value":"preferredGroupSize"}},{"kind":"Field","name":{"kind":"Name","value":"notificationEmail"}},{"kind":"Field","name":{"kind":"Name","value":"isGamificationEnabled"}}]}}]}}]} as unknown as DocumentNode<CreateCourseMutation, CreateCourseMutationVariables>;
 export const CreateFeedbackDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateFeedback"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"quizId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"content"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createFeedback"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"quizId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"quizId"}}},{"kind":"Argument","name":{"kind":"Name","value":"content"},"value":{"kind":"Variable","name":{"kind":"Name","value":"content"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isPublished"}},{"kind":"Field","name":{"kind":"Name","value":"isPinned"}},{"kind":"Field","name":{"kind":"Name","value":"isResolved"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"votes"}}]}}]}}]} as unknown as DocumentNode<CreateFeedbackMutation, CreateFeedbackMutationVariables>;
diff --git a/packages/graphql/src/public/client.json b/packages/graphql/src/public/client.json
index d2bae4a1ae..f259fbddd7 100644
--- a/packages/graphql/src/public/client.json
+++ b/packages/graphql/src/public/client.json
@@ -23,7 +23,7 @@
    "ChangeShortname": "07ed8db96ea01e17e68f1b598ba830a22199d44248cc473072fe2428491c4656",
    "ChangeUserGroupName": "a8efa0083d3cff34731ce21a28536512bccb280742dfa25a3fbde448c376168d",
    "ChangeUserLocale": "0a5f88b275c2319ba6c569426183657cdb7962b17651bf16a738f24766affd67",
-   "CreateAnswerCollection": "f3c2c7a4aca4617f9749c9f147622a9b280bd0a033f3134c6fa0256cb1089a33",
+   "CreateAnswerCollection": "8a22c3f7edcb7c7c3affd21ac45a22cbfd6e9b43e14d31617e2dacc972d2ad39",
    "CreateCatalogCollection": "2ae78e7c95d8cd59dd8f2274475e1f9b79ee6b85092bbea675dfa654305399a1",
    "CreateCourse": "189a624ca4fd8df46b23bc5d190dd07f08065cf518f1f862b949b8e658a823f1",
    "CreateFeedback": "5a4a9e710048cb5b679e7d8217907d623c7c53638fec41183a0257931fc8b136",
diff --git a/packages/graphql/src/public/server.json b/packages/graphql/src/public/server.json
index 399e297288..1a6b2ef4dd 100644
--- a/packages/graphql/src/public/server.json
+++ b/packages/graphql/src/public/server.json
@@ -23,7 +23,7 @@
    "07ed8db96ea01e17e68f1b598ba830a22199d44248cc473072fe2428491c4656": "mutation ChangeShortname($shortname: String!) {\n  changeShortname(shortname: $shortname) {\n    id\n    shortname\n    __typename\n  }\n}",
    "a8efa0083d3cff34731ce21a28536512bccb280742dfa25a3fbde448c376168d": "mutation ChangeUserGroupName($id: Int!, $name: String!) {\n  changeUserGroupName(id: $id, name: $name)\n}",
    "0a5f88b275c2319ba6c569426183657cdb7962b17651bf16a738f24766affd67": "mutation ChangeUserLocale($locale: LocaleType!) {\n  changeUserLocale(locale: $locale) {\n    id\n    locale\n    __typename\n  }\n}",
-   "f3c2c7a4aca4617f9749c9f147622a9b280bd0a033f3134c6fa0256cb1089a33": "mutation CreateAnswerCollection($name: String!, $description: String!, $answers: [String!]!) {\n  createAnswerCollection(\n    name: $name\n    description: $description\n    answers: $answers\n  ) {\n    id\n    name\n    description\n    ownerShortname\n    numSharedUsers\n    numOfEntries\n    permissionLevel\n    isOwner\n    isManager\n    isEditor\n    isImported\n    isShared\n    isDeletable\n    isRemovable\n    sharingType\n    __typename\n  }\n}",
+   "8a22c3f7edcb7c7c3affd21ac45a22cbfd6e9b43e14d31617e2dacc972d2ad39": "mutation CreateAnswerCollection($name: String!, $description: String!, $answers: [String!]!) {\n  createAnswerCollection(\n    name: $name\n    description: $description\n    answers: $answers\n  ) {\n    id\n    name\n    description\n    ownerShortname\n    numSharedUsers\n    numOfEntries\n    permissionLevel\n    isOwner\n    isManager\n    isEditor\n    isImported\n    isShared\n    isDeletable\n    isRemovable\n    sharingType\n    entries {\n      id\n      value\n      __typename\n    }\n    __typename\n  }\n}",
    "2ae78e7c95d8cd59dd8f2274475e1f9b79ee6b85092bbea675dfa654305399a1": "mutation CreateCatalogCollection($name: String!, $access: ObjectAccess!) {\n  createCatalogCollection(name: $name, access: $access) {\n    id\n    name\n    access\n    ownerShortname\n    isOwner\n    isManager\n    isRequested\n    isShared\n    __typename\n  }\n}",
    "189a624ca4fd8df46b23bc5d190dd07f08065cf518f1f862b949b8e658a823f1": "mutation CreateCourse($name: String!, $displayName: String!, $description: String, $color: String, $startDate: Date!, $endDate: Date!, $isGroupCreationEnabled: Boolean!, $groupDeadlineDate: Date!, $maxGroupSize: Int!, $preferredGroupSize: Int!, $notificationEmail: String, $isGamificationEnabled: Boolean!) {\n  createCourse(\n    name: $name\n    displayName: $displayName\n    description: $description\n    color: $color\n    startDate: $startDate\n    endDate: $endDate\n    isGroupCreationEnabled: $isGroupCreationEnabled\n    groupDeadlineDate: $groupDeadlineDate\n    maxGroupSize: $maxGroupSize\n    preferredGroupSize: $preferredGroupSize\n    notificationEmail: $notificationEmail\n    isGamificationEnabled: $isGamificationEnabled\n  ) {\n    id\n    name\n    displayName\n    description\n    color\n    startDate\n    endDate\n    isGroupCreationEnabled\n    groupDeadlineDate\n    maxGroupSize\n    preferredGroupSize\n    notificationEmail\n    isGamificationEnabled\n    __typename\n  }\n}",
    "5a4a9e710048cb5b679e7d8217907d623c7c53638fec41183a0257931fc8b136": "mutation CreateFeedback($quizId: String!, $content: String!) {\n  createFeedback(quizId: $quizId, content: $content) {\n    id\n    isPublished\n    isPinned\n    isResolved\n    content\n    votes\n    __typename\n  }\n}",
diff --git a/packages/graphql/src/schema/resource.ts b/packages/graphql/src/schema/resource.ts
index f6d91b93d3..0cd4c91cbe 100644
--- a/packages/graphql/src/schema/resource.ts
+++ b/packages/graphql/src/schema/resource.ts
@@ -64,11 +64,11 @@ export const AnswerCollection = AnswerCollectionRef.implement({
       nullable: true,
     }),
     numOfEntries: t.exposeInt('numOfEntries', { nullable: true }),
-
     permissionLevel: t.expose('permissionLevel', {
       type: PermissionLevel,
       nullable: true,
     }),
+
     ownerShortname: t.exposeString('ownerShortname', { nullable: true }),
     numSharedUsers: t.exposeInt('numSharedUsers', { nullable: true }),
     isOwner: t.exposeBoolean('isOwner', { nullable: true }),
diff --git a/packages/graphql/src/services/resources.ts b/packages/graphql/src/services/resources.ts
index b68374bbe7..8b4a219857 100644
--- a/packages/graphql/src/services/resources.ts
+++ b/packages/graphql/src/services/resources.ts
@@ -76,11 +76,7 @@ export async function createAnswerCollection(
         },
       },
       include: {
-        _count: {
-          select: {
-            entries: true,
-          },
-        },
+        entries: true,
       },
     })
 
@@ -96,7 +92,7 @@ export async function createAnswerCollection(
   return {
     ...collection,
     numSharedUsers: 0,
-    numOfEntries: collection._count.entries,
+    numOfEntries: collection.entries.length,
     isOwner: true,
     isManager: true,
     isEditor: true,
diff --git a/packages/i18n/messages/de.ts b/packages/i18n/messages/de.ts
index 5196144377..03a463f87f 100644
--- a/packages/i18n/messages/de.ts
+++ b/packages/i18n/messages/de.ts
@@ -1250,8 +1250,15 @@ Da die KlickerUZH-App noch nicht im iOS-App-Store verfügbar ist, folgen Sie die
       confirmCollectionChange:
         'Sind Sie sicher, dass Sie die Antwort-Sammlung wechseln möchten? Die bisher ausgewählten Elemente der Fallstudie und alle definierten Musterlösungen gehen durch diesen Wechsel verloren.',
       selectedItems: 'Ausgewählte Elemente',
+      definedItems: 'Neue Fallstudien-Elemente',
       caseStudyItemsTooltip:
         'Bitte wählen Sie hier die Elemente aus der Antwort-Sammlung, welche durch die Teilnehmer in der Fallstudie in bezug auf die unten erfassten Kriterien bewertet werden sollen.',
+      newCaseStudyItemsTooltip:
+        'Bitte geben Sie hier die Fallstudien-Elemente an, die in Bezug auf die angegebenen Kriterien bewertet werden sollen. Diese werden beim Speichern der Frage automatisch in eine neue Antwort-Sammlung kombiniert.',
+      enterItemsManually:
+        'Möchten Sie die Fallstudien-Elemente manuell eingeben?',
+      enterItemsManuallyExplanation:
+        'Diese Oberfläche erlaubt es Ihnen, Ihre Fallstudien-Elemente manuell einzugeben. Beim Speichern der Frage werden die entsprechenden Elemente automatisch in eine <b>neue Antwort-Sammlung kombiniert</b>, die Sie später wiederverwenden oder erweitern können. <button>Sie können auch wieder zur Auswahl von Fallstudien-Elementen aus einer bestehenden Sammlung wechseln.</button>',
       selectCaseStudyItems: 'Elemente auswählen...',
       caseStudyRangeCriterion: 'Numerisches Intervallkriterium',
       caseStudyStepCriterion: 'Schritt-/Likert-Kriterium',
@@ -1847,6 +1854,8 @@ Da die KlickerUZH-App noch nicht im iOS-App-Store verfügbar ist, folgen Sie die
         'Bitte wählen Sie eine Antwort-Sammlung aus welcher die zu bewertenden Element der Fallstudie selektiert werden.',
       CSItemsRequired:
         'Bitte wählen Sie mindestens ein Element aus der Antwort-Sammlung aus, welches von den Teilnehmenden auf die erstellten Kriterien bewertet werden soll.',
+      CSNewItemsRequired:
+        'Bitten definieren Sie mindestens ein Element, welches von den Teilnehmenden auf die erstellten Kriterien bewertet werden soll.',
       CSCriteriaNameRequired:
         'Bitte geben Sie einen Namen für das Kriterium ein.',
       CSCriteriaMinRequired:
diff --git a/packages/i18n/messages/en.ts b/packages/i18n/messages/en.ts
index b57d42264f..1cf743420d 100644
--- a/packages/i18n/messages/en.ts
+++ b/packages/i18n/messages/en.ts
@@ -1249,8 +1249,15 @@ Since the KlickerUZH app is not yet available on the iOS App Store, follow these
       confirmCollectionChange:
         'Are you sure you want to change the answer collection? The previously selected elements of the case study and all defined sample solutions will be lost due to this change.',
       selectedItems: 'Selected items',
+      definedItems: 'New case study items',
       caseStudyItemsTooltip:
         'Please select the elements from the answer collection that participants should evaluate in the case study according to the criteria recorded below.',
+      newCaseStudyItemsTooltip:
+        'Please enter the case study items that should be evaluated with respect to the specified criteria. They will automatically be combined into an answer collection when saving the question.',
+      enterItemsManually:
+        'Want to enter the case study items manually instead?',
+      enterItemsManuallyExplanation:
+        'This interface allows you to enter your case study items manually. When saving the question, the corresponding items will automatically be <b>combined into a new answer collection</b>, which you can re-use or extend later. <button>You may also switch back to the selection of case study items from an existing collection.</button>',
       selectCaseStudyItems: 'Select items...',
       caseStudyRangeCriterion: 'Numerical range criterion',
       caseStudyStepCriterion: 'Step / Likert criterion',
@@ -1822,6 +1829,8 @@ Since the KlickerUZH app is not yet available on the iOS App Store, follow these
         'Please select an answer collection from which the elements to be evaluated in the case study are selected.',
       CSItemsRequired:
         'Please select at least one item from the answer collection that participants should evaluate against the created criteria.',
+      CSNewItemsRequired:
+        'Please define at least one item that should be evaluated by the participants against the created criteria.',
       CSCriteriaNameRequired: 'Please enter a name for the criterion.',
       CSCriteriaMinRequired: 'Please enter a minimum value for the criterion.',
       CSCriteriaMinLessThanMax:


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (9)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts (1)</summary><blockquote>

`105-130`: **Items generation logic handles both selection modes correctly**

The modification to handle both `existing` and `new` item selection modes is implemented correctly. The code now constructs items either from filtered answer collection entries or from manually created items based on the `itemSelectionMode`.

However, the nested ternary expressions make the code a bit hard to follow. 


Consider restructuring the logic for better readability:

```diff
- items:
-   (!('itemSelectionMode' in values.options) ||
-     values.options.itemSelectionMode === 'existing') &&
-   typeof answerCollectionEntries !== 'undefined' &&
-   answerCollectionEntries.length > 0
-     ? answerCollectionEntries.flatMap((entry) => {
-         if (
-           'selectedItems' in values.options &&
-           values.options.selectedItems?.includes(entry.id)
-         ) {
-           return {
-             id: entry.id,
-             value: entry.value,
-           }
-         }
-
-         return []
-       })
-     : 'manuallyCreatedItems' in values.options
-       ? (values.options.manuallyCreatedItems?.map(
-           (item, index) => ({
-             id: index,
-             value: item,
-           })
-         ) ?? [])
-       : [],
+ items: (() => {
+   // Use existing answer collection items
+   const isExistingMode = !('itemSelectionMode' in values.options) || 
+     values.options.itemSelectionMode === 'existing';
+   
+   if (isExistingMode && 
+       typeof answerCollectionEntries !== 'undefined' && 
+       answerCollectionEntries.length > 0) {
+     return answerCollectionEntries.flatMap((entry) => {
+       if ('selectedItems' in values.options && 
+           values.options.selectedItems?.includes(entry.id)) {
+         return { id: entry.id, value: entry.value };
+       }
+       return [];
+     });
+   }
+   
+   // Use manually created items
+   if ('manuallyCreatedItems' in values.options) {
+     return values.options.manuallyCreatedItems?.map((item, index) => ({
+       id: index,
+       value: item,
+     })) ?? [];
+   }
+   
+   return [];
+ })(),
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx (1)</summary><blockquote>

`123-140`: **Added toggle to manual item creation mode**

The new button allows users to switch to manual item creation mode when in creation mode. The implementation correctly resets the selected items and updates the selection mode.

However, the button might benefit from clearer user guidance about what happens when clicked.


Consider adding a tooltip to explain the implications of switching modes:

```diff
  <Button
    basic
    onClick={() => {
      // reset the selected items
      setAnswerCollectionEntries([])
      setSelectedItems([])

      // switch to the creation mode for new answer collection options
      setItemSelectionMode('new')
    }}
+   tooltip={t('manage.elements.enterItemsManuallyTooltip')}
    className={{
      root: 'text-primary-100 hover:text-primary-100 w-max px-0.5 py-1 text-sm hover:bg-transparent hover:underline',
    }}
  >
    {t('manage.elements.enterItemsManually')}
  </Button>
```

This would require adding a new translation key that explains that switching modes will clear the current selection.

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx (1)</summary><blockquote>

`77-107`: **Implemented conditional rendering for item selection components**

The component now conditionally renders either `CaseStudyCollectionSelection` or `CaseStudyManualItemCreation` based on the selection mode and creation mode flags. The implementation correctly handles state transitions between the two modes.

The conditional rendering is somewhat complex with nested conditions. Consider adding a brief comment explaining the two possible flows to improve maintainability.


```diff
+  // When in "existing" mode or when mode is undefined, show the collection selection UI
   {(selectionMode.value === 'existing' ||
     typeof selectionMode.value === 'undefined') && (
     <CaseStudyCollectionSelection
       /* props */
     />
   )}
+  // When in creation mode and "new" mode is selected, show the manual item creation UI
   {creationMode && selectionMode.value === 'new' && (
     <CaseStudyManualItemCreation
       /* props */
     />
   )}
```

</blockquote></details>
<details>
<summary>packages/i18n/messages/de.ts (1)</summary><blockquote>

`1857-1858`: **Minor typo in validation message text.**

There's a small typo in the validation message - "Bitten" should be "Bitte" (without the 'n').

```diff
-      CSNewItemsRequired:
-        'Bitten definieren Sie mindestens ein Element, welches von den Teilnehmenden auf die erstellten Kriterien bewertet werden soll.',
+      CSNewItemsRequired:
+        'Bitte definieren Sie mindestens ein Element, welches von den Teilnehmenden auf die erstellten Kriterien bewertet werden soll.',
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts (2)</summary><blockquote>

`12-28`: **Consider consistent return type for better type safety.**

The hook returns different types in different code paths ([] or null), which could lead to unexpected behavior for consumers. Consider returning a consistent type (e.g., always return an array, even when empty).

```diff
- return null
+ return []
```

---

`30-32`: **Missing trailing newline at EOF.**

The file is missing a trailing newline at the end, which is generally considered a good practice for text files.

```diff
 export default useSelectedNewItems
-32
+
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/ElementEditForm.tsx (1)</summary><blockquote>

`110-123`: **Remove duplicate setFailureToast call.**

There's a redundant duplicate call to setFailureToast(true), which should be removed.

```diff
 if (!success) {
   setFailureToast(true)
-  setFailureToast(true)
 } else {
   onSuccess()
 }
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx (1)</summary><blockquote>

`287-376`: **Well-implemented logic for handling manually created items.**

The implementation correctly:
1. Creates a deep copy of the form values
2. Creates a new answer collection when in 'new' mode
3. Updates the Apollo cache
4. Maps the manually created items to the new entry IDs
5. Updates the solution references with the new item IDs

This allows for a seamless experience when creating case study items inline.


However, consider extracting the complex mapping logic for solution IDs into a separate helper function to improve readability.

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (1)</summary><blockquote>

`24-26`: **Consider using more descriptive variable naming.**

You're using double underscores (`____`) to ignore the meta field from `useField`. Consider using more descriptive naming like `_metaField` or simply `_` for better code readability.

```diff
-const [manualItemsField, ____, manualItemsHelpers] = useField<string[]>(
+const [manualItemsField, _, manualItemsHelpers] = useField<string[]>(
  'options.manuallyCreatedItems'
)
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b1385627ef3001c6dab95e7490278adb7a0ffd6a and 1014920ef6a69ca1e1df47fff8a787fce0292c84.

</details>

<details>
<summary>⛔ Files ignored due to path filters (4)</summary>

* `packages/graphql/src/graphql/ops/MCreateAnswerCollection.graphql` is excluded by `!**/**/graphql/ops/**`
* `packages/graphql/src/ops.ts` is excluded by `!**/**/ops.ts`
* `packages/graphql/src/public/client.json` is excluded by `!**/**/public/**`
* `packages/graphql/src/public/server.json` is excluded by `!**/**/public/**`

</details>

<details>
<summary>📒 Files selected for processing (20)</summary>

* `apps/frontend-manage/src/components/activities/templates/TemplateNewElementModal.tsx` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/ElementEditForm.tsx` (4 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx` (11 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/ElementFormErrors.tsx` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/ElementTypeMonitor.tsx` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/helpers.ts` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx` (5 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx` (5 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/useSelectedAnswerEntry.ts` (2 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/types.ts` (2 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts` (2 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/useElementFormInitialValues.ts` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts` (2 hunks)
* `apps/frontend-manage/src/pages/index.tsx` (0 hunks)
* `packages/graphql/src/schema/resource.ts` (1 hunks)
* `packages/graphql/src/services/resources.ts` (3 hunks)
* `packages/i18n/messages/de.ts` (2 hunks)
* `packages/i18n/messages/en.ts` (2 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* apps/frontend-manage/src/pages/index.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx (1)</summary>

```
Learnt from: sjschlapbach
PR: uzh-bf/klicker-uzh#4498
File: apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx:32-32
Timestamp: 2025-02-05T15:07:43.967Z
Learning: The codebase uses two distinct GraphQL queries for answer collections:
1. `GetAnswerCollectionsInfoDocument`: A lightweight query designed for overview pages, fetching minimal information
2. `GetAnswerCollectionsDocument`: A detailed query used in dropdowns and interactive components requiring complete collection data
```

</details>

</details><details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx (1)</summary><blockquote>

<details>
<summary>packages/graphql/src/schema/resource.ts (1)</summary>

* `AnswerCollection` (57-83)

</details>

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx (1)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCasesFields.tsx (1)</summary>

* `CaseStudySetterProps` (23-35)

</details>

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts (1)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/types.ts (1)</summary>

* `ElementFormTypesCaseStudy` (99-116)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts</summary>

[error] 455-455: Do not add then to an object.



(lint/suspicious/noThenProperty)

---

[error] 464-464: Do not add then to an object.



(lint/suspicious/noThenProperty)

---

[error] 475-475: Do not add then to an object.



(lint/suspicious/noThenProperty)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (9)</summary>

* GitHub Check: build
* GitHub Check: check
* GitHub Check: Analyze (javascript)
* GitHub Check: format
* GitHub Check: test
* GitHub Check: SonarCloud
* GitHub Check: test
* GitHub Check: cypress-run
* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (41)</summary><blockquote>

<details>
<summary>packages/graphql/src/schema/resource.ts (1)</summary>

`71-71`: **Good whitespace formatting change.**

This small formatting change improves code readability by adding a consistent line break after the permissionLevel field definition in the AnswerCollection type.

</details>
<details>
<summary>apps/frontend-manage/src/components/activities/templates/TemplateNewElementModal.tsx (1)</summary>

`40-43`: **Return value now correctly matches Promise<boolean> contract.**

The explicit `return true` statement ensures the function fulfills its contract of returning a boolean indicating success, aligning with the form's updated error handling approach where toast management has been moved inside ElementEditForm.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/ElementTypeMonitor.tsx (1)</summary>

`39-39`: **Properly updated useEffect dependency array.**

The expanded dependency array now correctly includes all dependencies used within the effect, ensuring proper re-execution when any of the function references change. This is particularly important for the new case study item handling where form validation needs to respond to dynamic form states.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/helpers.ts (1)</summary>

`265-265`: <details>
<summary>✅ Verification successful</summary>

**Added non-null assertion to handle new optional answerCollection.**

The added non-null assertion (`!`) ensures type safety when parsing the answerCollection value, which is now optional in the ElementFormTypesCaseStudy type to support the new manual item creation mode. This change correctly handles the dual-mode workflow where answerCollection is only required when itemSelectionMode is 'existing'.


Let's verify that the answerCollection is indeed optional in the updated type definition:

---

🏁 Script executed:

```shell
#!/bin/bash
# Look for the updated ElementFormTypesCaseStudy type definition that includes itemSelectionMode
grep -A 30 "ElementFormTypesCaseStudy" apps/frontend-manage/src/components/questions/manipulation/types.ts
```

Length of output: 1786

---



</details>

**Approve non-null assertion on answerCollection**

The `ElementFormTypesCaseStudy` interface defines `answerCollection?: string`, so using the non-null assertion (`values.options.answerCollection!`) before `parseInt` is correct to satisfy the type system. No further changes are required.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/ElementFormErrors.tsx (1)</summary>

`215-220`: **Good addition of error handling for manually created items.**

The new error display section for `manuallyCreatedItems` follows the established pattern and integrates well with the existing error handling system. It correctly renders the localized label for "definedItems" alongside the corresponding error message.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/useElementFormInitialValues.ts (1)</summary>

`153-154`: **Proper initialization of new case study item fields.**

The changes properly initialize two new fields with reasonable defaults:
1. `itemSelectionMode` set to 'existing' - indicates the default mode is selecting from existing collections
2. `manuallyCreatedItems` as an empty array - prepares for potential manual item creation

The comment clarifies that manual definition is not supported for element editing by default, which is valuable information for maintainers.



Also applies to: 158-159

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/useSelectedAnswerEntry.ts (1)</summary>

`7-7`: **Well implemented mode handling for answer entry selection.**

The hook now correctly disables processing of selected answers when in 'new' mode, which is appropriate for the dual-mode case study item workflow. The parameter is properly typed and added to the dependency array, ensuring the hook re-evaluates when the mode changes.



Also applies to: 12-12, 16-16, 31-31

</details>
<details>
<summary>packages/graphql/src/services/resources.ts (1)</summary>

`79-79`: **Enhanced collection creation with full entry data.**

The `createAnswerCollection` function now returns complete entry data instead of just the count, which is essential for the new manual item creation workflow. This ensures proper integration between backend and frontend when creating new collections from manually defined items.



Also applies to: 95-96, 104-107

</details>
<details>
<summary>packages/i18n/messages/en.ts (4)</summary>

`1252-1252`: **New label added for manually created case study items.**

This new label provides a clear indication for the section where users can define new case study items manually.

---

`1255-1256`: **Clear explanation added for manual case study item creation.**

This tooltip effectively explains how manually entered items will be automatically combined into an answer collection when saving, helping users understand the behavior of this new feature.

---

`1258-1260`: **Added toggle option for switching to manual entry mode.**

These strings support the workflow toggle between selecting existing collections and manually defining case study items. The explanation effectively communicates that:
1. Items will be combined into a reusable answer collection
2. Users can switch back to the selection mode if needed

The HTML formatting appropriately highlights the key information about collection creation.

---

`1832-1833`: **Added validation message for manually defined items.**

This validation message complements the existing validation for selected items (line 1831), ensuring users provide at least one item for evaluation when using the manual entry mode.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx (3)</summary>

`15-41`: **Updated props interface to support item selection mode switching**

The component's interface has been extended to support the new `creationMode`, `itemSelectionMode`, and `setItemSelectionMode` props, which enables toggling between existing collection selection and manual item creation modes.

---

`66-67`: **Updated useSelectedAnswerEntry hook call with new parameter**

The hook call now correctly passes the `itemSelectionMode` parameter, which allows the hook to conditionally return an empty selection when in `'new'` mode.

---

`79-79`: **Adjusted container flex layout**

The container has been updated with a flex layout and negative margin to improve the spacing between elements.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx (1)</summary>

`37-40`: **Added Formik field hook for item selection mode**

The component now uses Formik's `useField` hook to manage the `itemSelectionMode` state, which enables toggling between existing and new item selection modes.

</details>
<details>
<summary>packages/i18n/messages/de.ts (4)</summary>

`1253-1253`: **LGTM! Clear label for new case study items.**

The new label "Neue Fallstudien-Elemente" is clear and consistent with the existing terminology in the file.

---

`1256-1257`: **LGTM! Informative tooltip for new feature.**

The tooltip clearly explains what happens when users manually enter case study items - they'll be automatically combined into a new answer collection when saving.

---

`1258-1259`: **LGTM! Prompt for enabling manual entry.**

This prompt is clear and aligns with the feature's purpose of allowing users to choose between selecting existing items or defining new ones manually.

---

`1260-1261`: **LGTM! Comprehensive explanation of manual entry workflow.**

The explanation clearly describes the workflow and highlights that:
1. Elements will be combined into a new answer collection
2. The collection can be reused later
3. Users can switch back to selecting from existing collections

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts (1)</summary>

`1-11`: **Hook interface looks good with clear type definitions.**

The hook accepts createdItems, itemSelectionMode, and an optional setSelectedItems function with proper TypeScript typing.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/types.ts (2)</summary>

`75-82`: **Good addition of clarifying comments for record key semantics.**

The comments explaining string key semantics for case study solutions improve code maintainability.

---

`103-108`: **Type changes correctly support new item selection modes.**

The updated interface properly supports both existing and new item selection modes by:
1. Adding the optional itemSelectionMode property
2. Making previously required fields optional
3. Adding the new manuallyCreatedItems array

This allows for flexible handling of both workflows while maintaining type safety.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/ElementEditForm.tsx (3)</summary>

`77-78`: **Return type change improves error handling.**

Changing the return type to Promise<boolean> enables more explicit success/failure handling in the form submission process.

---

`89-90`: **Internal failure toast state improves component encapsulation.**

Moving the failure toast state into the component simplifies the API and improves encapsulation.

---

`274-277`: **Good conditional prop for new creation mode.**

Adding the creationMode prop to CaseStudyOptions component correctly determines when to show the manual item creation UI based on form mode.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx (4)</summary>

`2-36`: **Imports structured appropriately.**

The additional imports for CreateAnswerCollectionDocument and type definitions are well organized.

---

`106-107`: **Added mutation hook for creating answer collections.**

The new createAnswerCollection mutation hook enables the creation of answer collections directly from the form.

---

`162-163`: **Simplified error handling with boolean returns.**

Error handling is consistently updated across all element types to return false on failure, aligning with the new return type.



Also applies to: 185-186, 210-211, 233-234, 256-257, 279-280, 393-394

---

`414-415`: **Return true on success.**

Explicitly returning true on successful completion ensures consistent behavior with the error paths that return false.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (5)</summary>

`8-22`: **Props interface is well-structured and complete.**

The component properly defines its props with clear types, making it easy to understand its requirements and usage. The type definitions align well with the component's functionality for manual item creation.

---

`28-33`: **Good use of custom hook for state synchronization.**

The component effectively uses the `useSelectedNewItems` hook to keep the selected items state in sync with manually created items. This is a clean separation of concerns.

---

`36-55`: **Well-implemented user notification with actionable text.**

The notification provides clear guidance with formatted text and an actionable element to switch modes. The rich text implementation with the `next-intl` library is well done.

---

`64-81`: **Well-configured react-select Creatable component.**

The component is properly configured with the necessary props and styling. The value and options mapping is correctly implemented.

---

`109-111`: **Internationalization is properly implemented.**

The component correctly uses translations for placeholder text and the "no options" message, ensuring a consistent user experience across different languages.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts (6)</summary>

`452-453`: **Good addition of itemSelectionMode validation.**

The validation for `itemSelectionMode` is correctly implemented as a string with the expected values 'existing' and 'new'.

---

`453-458`: **Fix suspicious 'then' property warning in Yup validation.**

The static analysis is flagging the use of `.then()` in this object. This is a known issue with certain linters that don't recognize Yup's API.

This appears to be a false positive from the static analyzer. You can safely ignore this warning as it's a valid Yup schema construction pattern. However, if you want to silence the warning, you could refactor to use the newer Yup's `.when()` with an explicit function callback:

```typescript
answerCollection: yup.string().when('itemSelectionMode', (itemSelectionMode, schema) => {
  return (!itemSelectionMode || itemSelectionMode === 'existing')
    ? schema.required(t('manage.formErrors.CSAnswerCollectionRequired'))
    : schema;
})
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 455-455: Do not add then to an object.



(lint/suspicious/noThenProperty)

</details>

</details>

---

`459-469`: **Conditional validation for selectedItems is well implemented.**

The validation logic correctly makes `selectedItems` required only when `itemSelectionMode` is 'existing' or undefined, which aligns with the component behavior.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 464-464: Do not add then to an object.



(lint/suspicious/noThenProperty)

</details>

</details>

---

`470-480`: **Clear validation rules for manuallyCreatedItems.**

The validation for manually created items is properly implemented, requiring at least one item when in 'new' mode. This ensures the form can't be submitted without creating items.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 475-475: Do not add then to an object.



(lint/suspicious/noThenProperty)

</details>

</details>

---

`612-629`: **Dynamic item selection based on mode is well implemented.**

The code correctly determines which items to use for validation based on the `itemSelectionMode`. When in 'new' mode, it creates an array of indices based on the length of `createdItems`, which is a smart approach.

---

`630-642`: **The solution validation logic is thorough and adaptable.**

The validation properly checks that there are solutions for all items, regardless of which mode is used. The dynamic selection of items ensures the validation works consistently across both modes.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T16:07:10Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts (2)</summary><blockquote>

`1-2`: **Add JSDoc comments to improve code documentation.**

This new hook would benefit from JSDoc comments explaining its purpose, parameters, and return value.


```diff
 import { Dispatch, SetStateAction, useMemo } from 'react'
 import { ElementFormTypesCaseStudy } from '../types'
 
+/**
+ * A hook that synchronizes manually created case study items with the selected items state.
+ * @param createdItems - The manually created items from the form
+ * @param itemSelectionMode - The current item selection mode ('existing' or 'new')
+ * @param setSelectedItems - Optional setter function for updating selected items state
+ * @returns An empty array when no synchronization is needed, or null after synchronization
+ */
 function useSelectedNewItems({
```

---

`13-27`: **Return value consistency could be improved.**

The hook returns an empty array in some cases and `null` in others, which could lead to confusing behavior for consumers of this hook.


Consider making the return value consistent by always returning an array:

```diff
   return useMemo(() => {
     if (
       typeof createdItems === 'undefined' ||
       createdItems.length === 0 ||
       typeof itemSelectionMode === 'undefined' ||
       itemSelectionMode === 'existing'
     ) {
       return []
     }

     setSelectedItems?.(
       createdItems.map((item) => ({ id: item.id, name: item.value }))
     )
-    return null
+    return createdItems.map((item) => ({ id: item.id, name: item.value }))
   }, [createdItems, itemSelectionMode, setSelectedItems])
```

</blockquote></details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts (1)</summary><blockquote>

`626-746`: **Consider extracting the complex validation logic into separate functions.**

The solution validation logic is quite complex and deeply nested, making it difficult to understand and maintain. Consider extracting parts of this validation into separate helper functions.


For example, you could extract the item validation logic, criterion validation logic, and bound checking into separate functions to improve readability.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1014920ef6a69ca1e1df47fff8a787fce0292c84 and a6cfbedc158a71aae857da46cb12f7aa5beda9cd.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx` (11 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCriterionModeMonitor.tsx` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/SelectionOptions.tsx` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts` (1 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/types.ts` (2 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts` (2 hunks)
* `apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts` (2 hunks)
* `packages/graphql/src/services/resources.ts` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCriterionModeMonitor.tsx

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (5)</summary>

* apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts
* apps/frontend-manage/src/components/questions/manipulation/types.ts
* packages/graphql/src/services/resources.ts
* apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx
* apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts (1)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/types.ts (1)</summary>

* `ElementFormTypesCaseStudy` (99-116)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts</summary>

[error] 455-455: Do not add then to an object.



(lint/suspicious/noThenProperty)

---

[error] 464-464: Do not add then to an object.



(lint/suspicious/noThenProperty)

---

[error] 480-480: Do not add then to an object.



(lint/suspicious/noThenProperty)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (6)</summary>

* GitHub Check: cypress-run
* GitHub Check: Analyze (javascript)
* GitHub Check: build
* GitHub Check: SonarCloud
* GitHub Check: test
* GitHub Check: check

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/SelectionOptions.tsx (1)</summary>

`59-63`: **LGTM - Clear implementation of the selection mode.**

The addition of `itemSelectionMode: 'existing'` parameter is clean and well-integrated into the existing code. This parameter explicitly marks this component as using the traditional flow of selecting from existing answer collections, which fits perfectly with the PR objective of supporting both existing and new inline case study item definition workflows.

</details>
<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts (3)</summary>

`452-485`: **Good implementation of conditional validation logic for the new item selection modes.**

The validation schema correctly implements the conditional logic for the two modes:
1. When using 'existing' items: requires answerCollection and selectedItems
2. When using 'new' items: requires manuallyCreatedItems

This is well-structured and aligns with the frontend feature requirements.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 455-455: Do not add then to an object.



(lint/suspicious/noThenProperty)

---

[error] 464-464: Do not add then to an object.



(lint/suspicious/noThenProperty)

---

[error] 480-480: Do not add then to an object.



(lint/suspicious/noThenProperty)

</details>

</details>

---

`617-631`: **Clear and maintainable solution for dynamic item selection.**

The solution validation logic now elegantly handles both item selection modes by determining the appropriate item source based on the mode setting.


```diff
-              // set the answer collection items according to the mode setting
               const items =
                 typeof itemMode === 'undefined' || itemMode === 'existing'
                   ? (selectedItems ?? [])
                   : (createdItems?.map((item) => item.id) ?? [])
```

---

`452-458`: <details>
<summary>❓ Verification inconclusive</summary>

**Fix the Yup validation method to avoid static analysis warnings.**

The static analysis tool Biome is reporting errors due to how the `when` method is being used.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if this is a biome-specific warning or a real issue with Yup's API usage

# Verify the Yup version being used in package.json
grep -r "yup\"" --include="package.json" .

# Check if there are other places using the same pattern that might give insights
grep -r "yup.*when.*then.*otherwise" --include="*.ts" --include="*.tsx" .
```

Length of output: 447

---



</details>

**Verify Biome warning for Yup `.when` usage**

I wasn’t able to reproduce the Biome error locally against Yup 1.4.0’s object overload of `.when({ is, then, otherwise })`. Can you share the exact warning message from Biome’s output? That will help confirm whether:

- the object‐style overload is being mis‐typed under our Yup version, or  
- switching to the functional overload resolves the issue

As a quick experiment, you can try the functional form:

```ts
answerCollection: yup.string().when(
  'itemSelectionMode',
  (mode, schema) =>
    !mode || mode === 'existing'
      ? schema.required(t('manage.formErrors.CSAnswerCollectionRequired'))
      : schema
),
```

and see if Biome stops reporting the warning. Please paste the Biome error so we can pinpoint the fix.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 455-455: Do not add then to an object.



(lint/suspicious/noThenProperty)

</details>

</details>

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T16:55:29Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (2)</summary><blockquote>

`84-91`: **Add validation for empty or whitespace-only items.**

The current implementation doesn't validate whether items contain only whitespace. This could lead to invalid data being stored.

```diff
onChange={(newValue) => {
  // set the new collection items
  const prevItems = manualItemsField.value ?? []
  const newItems = newValue.map((item) => ({
    id: item.value,
-   value: item.label,
+   value: item.label.trim(),
  }))
+  // Filter out any items with empty values after trimming
+  const validItems = newItems.filter((item) => item.value.length > 0)
-  manualItemsHelpers.setValue(newItems)
+  manualItemsHelpers.setValue(validItems)
```

---

`136-154`: _⚠️ Potential issue_

**Validate new items before adding them and improve ID handling.**

The onCreateOption function has several issues:
1. It doesn't validate that new tags aren't empty or whitespace-only
2. It uses random number generation for IDs, which could potentially cause duplicates
3. There's an inconsistency between the IDs used in the form state vs. answer collection entries

```diff
onCreateOption={(newValue) => {
+  // Trim the new value and check if it's not empty
+  const trimmedValue = newValue.trim()
+  if (trimmedValue.length === 0) return
+
  // add the new tag to the list of new collection items, if it does not exist already
  if (
    !manualItemsField.value?.some(
-     (existingItem) => existingItem.value === newValue
+     (existingItem) => existingItem.value === trimmedValue
    )
  ) {
+    // Generate a new ID that's unique among existing items
+    const existingIds = manualItemsField.value?.map(item => item.id) || []
+    const newId = Math.max(0, ...existingIds) + 1
+    
    manualItemsHelpers.setValue([
      ...(manualItemsField.value ?? []),
-     { id: Math.floor(Math.random() * 1000000 + 1), value: newValue },
+     { id: newId, value: trimmedValue },
    ])

    // update the answer collection state for correct validation
    setAnswerCollectionEntries((prev) => [
      ...prev,
-     { id: prev.length, value: newValue },
+     { id: newId, value: trimmedValue },
    ])
  }
}}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (3)</summary><blockquote>

`25-29`: **Consider using standard naming conventions for unused variables.**

The use of multiple underscores (`____` and `___`) as placeholders for unused variables is unconventional and may reduce code readability. Consider using the standard single underscore (`_`) for unused variables.

```diff
- const [manualItemsField, ____, manualItemsHelpers] = useField<
+ const [manualItemsField, _, manualItemsHelpers] = useField<
    ElementFormTypesCaseStudy['options']['manuallyCreatedItems']
  >('options.manuallyCreatedItems')
- const [casesField, ___, casesHelpers] =
+ const [casesField, _, casesHelpers] =
    useField<ElementFormTypesCaseStudy['options']['cases']>('options.cases')
```

---

`101-124`: **Add safeguard for casesField.value being undefined.**

When handling the case where an item is removed, the code assumes casesField.value is not undefined. While the usage of the optional chaining operator (`?.`) prevents errors, it would be clearer to handle this explicitly.

```diff
// if an item has been removed, remove the corresponding key from the case solutions
if (removedItem) {
+  if (!casesField.value) {
+    return;
+  }
+  
  const newCases = casesField.value?.map((caseItem) => {
    // if no solutions are set, skip this case
    if (!('solutions' in caseItem) || !caseItem.solutions) {
      return caseItem
    }

    // filter out all solution entries for the removed item
    const newSolutions = Object.fromEntries(
      Object.entries(caseItem.solutions).filter(
        ([itemIdString]) =>
          itemIdString !== `itemId-${removedItem.id}`
      )
    )

    return {
      ...caseItem,
      solutions: newSolutions,
    }
  })

  // update the cases field
  casesHelpers.setValue(newCases)
}
```

---

`128-135`: **Ensure consistent item IDs between form state and UI state.**

The current implementation might lead to inconsistency between the IDs used in the form state and the answer collection entries, especially when both adding and removing items. Consider using the same IDs consistently.

```diff
// update the answer collection state for correct validation
setAnswerCollectionEntries(
-  newItems.map((item) => ({
-    id: item.id,
-    value: item.value,
-  }))
+  // Use validItems if you implemented the validation suggested earlier
+  newItems.map((item) => item)
)
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a6cfbedc158a71aae857da46cb12f7aa5beda9cd and d2f6b8c259d36708e4f5f61d670a4592ea85e1ea.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (1)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/types.ts (1)</summary>

* `ElementFormTypesCaseStudy` (99-116)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (9)</summary>

* GitHub Check: test
* GitHub Check: SonarCloud
* GitHub Check: cypress-run
* GitHub Check: build
* GitHub Check: Analyze (javascript)
* GitHub Check: check
* GitHub Check: test
* GitHub Check: format
* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (1)</summary>

`1-163`: **The component appears well-structured and implements the required functionality.**

The `CaseStudyManualItemCreation` component provides a solid foundation for manually creating case study items. The code is generally well-organized and follows good React practices with appropriate use of hooks and state management. The incorporation of internationalization, tooltips, and user notifications enhances the user experience.

After addressing the validation issues for empty/whitespace values and improving the ID handling approach, this component should work reliably within the form context.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T17:03:19Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (4)</summary><blockquote>

`73-90`: **Validate and filter out empty items after trimming.**

The `onChange` handler trims item values but doesn't filter out items that might become empty after trimming. This could allow empty or whitespace-only items to be stored.


```diff
onChange={(newValue) => {
  // set the new collection items
  const prevItems = manualItemsField.value ?? []
  const newItems = newValue.map((item) => ({
    id: item.value,
-   value: item.label.trim(),
+   value: item.label.trim(),
  }))
+ // Filter out any items that are empty after trimming
+ const filteredItems = newItems.filter((item) => item.value.length > 0)
- manualItemsHelpers.setValue(newItems)
+ manualItemsHelpers.setValue(filteredItems)

  // check if an item has been removed and conditionally remove the solutions for this item
```

---

`136-148`: **Validate new items before adding them.**

When creating a new option, you trim the value but don't check if it's empty after trimming. This could allow empty or whitespace-only items to be added.


```diff
onCreateOption={(newValue) => {
+ // Trim the new tag and check if it's not empty
+ const trimmedValue = newValue.trim()
+ if (trimmedValue.length === 0) return
+
  // add the new tag to the list of new collection items, if it does not exist already
  if (
    !manualItemsField.value?.some(
-     (existingItem) => existingItem.value === newValue.trim()
+     (existingItem) => existingItem.value === trimmedValue
    )
  ) {
    manualItemsHelpers.setValue([
      ...(manualItemsField.value ?? []),
      {
        id: Math.floor(Math.random() * 1000000 + 1),
-       value: newValue.trim(),
+       value: trimmedValue,
      },
    ])
```

---

`145-148`: **Replace random ID generation with a more reliable approach.**

Using `Math.random()` for generating IDs can potentially lead to duplicate IDs. Consider using a more reliable ID generation method.


```diff
- id: Math.floor(Math.random() * 1000000 + 1),
+ id: Date.now() + (manualItemsField.value?.length || 0),
  value: newValue.trim(),
```

Alternatively, consider using a library like `uuid` if available in your project.

---

`94-126`: **Add a confirmation step before dropping associated case-study solutions.**

Before automatically removing solutions for a deleted item, the application should prompt the user to confirm, especially if that item had answers in case studies.


Consider implementing a confirmation dialog before removing item solutions:

```diff
// if an item has been removed, remove the corresponding key from the case solutions
if (removedItem) {
+ // Check if the removed item has any associated solutions
+ const hasAssociatedSolutions = casesField.value?.some(caseItem => 
+   caseItem.solutions && `itemId-${removedItem.id}` in caseItem.solutions
+ );
+ 
+ if (hasAssociatedSolutions) {
+   // You could use an existing confirmation modal component here
+   const confirmed = confirm(t('manage.elements.confirmRemoveItemWithSolutions'));
+   
+   if (!confirmed) {
+     // Restore the previous items if user cancels
+     manualItemsHelpers.setValue(prevItems);
+     
+     // Also restore the answer collection entries
+     setAnswerCollectionEntries(
+       prevItems.map((item) => ({
+         id: item.id,
+         value: item.value,
+       }))
+     );
+     return;
+   }
+ }
+
  const newCases = casesField.value?.map((caseItem) => {
    // if no solutions are set, skip this case
    if (!('solutions' in caseItem) || !caseItem.solutions) {
      return caseItem
    }
```

**Note:** For a production implementation, you should use a proper modal component from your design system rather than the native `confirm()` method.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (1)</summary><blockquote>

`86-90`: **Check for duplicates when updating items.**

While there's a check to prevent adding duplicate items in `onCreateOption`, there's no similar check in the `onChange` handler. This could potentially lead to duplicate items if users manipulate the input in certain ways.


```diff
// set the new collection items
const prevItems = manualItemsField.value ?? []
const newItems = newValue.map((item) => ({
  id: item.value,
  value: item.label.trim(),
}))
+ // Ensure no duplicate values
+ const uniqueItems = [];
+ const seenValues = new Set();
+ for (const item of newItems) {
+   if (item.value && !seenValues.has(item.value)) {
+     seenValues.add(item.value);
+     uniqueItems.push(item);
+   }
+ }
- manualItemsHelpers.setValue(newItems)
+ manualItemsHelpers.setValue(uniqueItems)
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d2f6b8c259d36708e4f5f61d670a4592ea85e1ea and f230799eaec164b00f69c1f4dcc1c48c126cfb76.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (8)</summary>

* GitHub Check: cypress-run
* GitHub Check: build
* GitHub Check: Analyze (javascript)
* GitHub Check: build
* GitHub Check: check
* GitHub Check: test
* GitHub Check: SonarCloud
* GitHub Check: format

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx (3)</summary>

`1-166`: **Approve the component overall structure and implementation.**

The component successfully implements the feature to allow manual creation of case study items inline. The component is well-structured with clear responsibility separation, effective use of hooks, proper internationalization, and good UI feedback for users.

With the suggested improvements for validation, ID generation, and confirmation dialogs, this will be a robust implementation.

---

`32-36`: <details>
<summary>❌ Incorrect review comment</summary>

**Consider handling edge case for missing selectedItems.**

While the code correctly handles when `manualItemsField.value` is undefined by using the nullish coalescing operator, there's no check if `setSelectedItems` is undefined, which could potentially lead to errors in certain scenarios.

---

🏁 Script executed:

```shell
#!/bin/bash
# Let's check if this component is used anywhere without providing the setSelectedItems prop
echo "Checking for usages of CaseStudyManualItemCreation..."
rg "CaseStudyManualItemCreation" --type tsx
```

Length of output: 188

---

I’ll locate the component definition and verify whether `setSelectedItems` is ever optional or omitted.

---

🏁 Script executed:

```shell
#!/bin/bash
echo "Searching for CaseStudyManualItemCreation definition and usages:"
rg "CaseStudyManualItemCreation" -n -g "*.tsx"
echo ""
echo "Searching for all setSelectedItems references in TSX files:"
rg "setSelectedItems" -n -g "*.tsx"
```

Length of output: 3159

---



</details>

**No guard needed for `setSelectedItems` – it’s a required prop**

The `CaseStudyManualItemCreation` component signature requires a `setSelectedItems: Dispatch<…>` prop, and every usage (e.g. in `CaseStudyOptions.tsx`) passes a valid setter from `useState`. There is no scenario where `setSelectedItems` could be `undefined`, so no additional runtime check is necessary. 

> Likely an incorrect or invalid review comment.

---

`152-155`: _🛠️ Refactor suggestion_

**Ensure consistent ID handling between different states.**

The ID used in `setAnswerCollectionEntries` (array length) is different from the ID in the form state (random number), which could lead to inconsistencies in item tracking.


```diff
// update the answer collection state for correct validation
setAnswerCollectionEntries((prev) => [
  ...prev,
- { id: prev.length, value: newValue.trim() },
+ { id: manualItemsField.value?.[manualItemsField.value.length - 1].id || 0, value: trimmedValue },
])
```



> Likely an incorrect or invalid review comment.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-21T17:06:33Z

Review comments:
diff_hunk: @@ -0,0 +1,116 @@
+import { FormLabel, UserNotification } from '@uzh-bf/design-system'
+import { useField } from 'formik'
+import { useTranslations } from 'next-intl'
+import { Dispatch, SetStateAction } from 'react'
+import Creatable from 'react-select/creatable'
+import useSelectedNewItems from './useSelectedNewItems'
+
+function CaseStudyManualItemCreation({
+  disabled,
+  itemSelectionMode,
+  setItemSelectionMode,
+  setAnswerCollectionEntries,
+  setSelectedItems,
+}: {
+  disabled: boolean
+  itemSelectionMode: 'existing' | 'new'
+  setItemSelectionMode: (newValue: 'existing' | 'new') => void
+  setAnswerCollectionEntries: Dispatch<
+    SetStateAction<{ id: number; value: string }[]>
+  >
+  setSelectedItems: Dispatch<SetStateAction<{ id: number; name: string }[]>>
+}) {
+  const t = useTranslations()
+  const [manualItemsField, ____, manualItemsHelpers] = useField<string[]>(
+    'options.manuallyCreatedItems'
+  )
+
+  // update the selected items state whenever the manually created items change
+  useSelectedNewItems({
+    createdItems: manualItemsField.value ?? [],
+    itemSelectionMode,
+    setSelectedItems,
+  })
+
+  return (
+    <div>
+      <UserNotification type="info">
+        {t.rich('manage.elements.enterItemsManuallyExplanation', {
+          b: (text) => <b>{text}</b>,
+          button: (text) => (
+            <span
+              onClick={() => {
+                // reset the selected items
+                setAnswerCollectionEntries([])
+
+                // switch to the selection mode for existing answer collections
+                setItemSelectionMode('existing')
+              }}
+              className="cursor-pointer hover:underline"
+            >
+              {text}
+            </span>
+          ),
+        })}
+      </UserNotification>
+
+      <FormLabel
+        required
+        label={t('shared.generic.caseStudyItems')}
+        tooltip={t('manage.elements.newCaseStudyItemsTooltip')}
+        labelType="small"
+        className={{ label: 'mt-2' }}
+      />
+      <Creatable
+        isClearable
+        isMulti
+        isDisabled={disabled}
+        value={
+          manualItemsField.value?.map((item) => ({
+            label: item,
+            value: item,
+          })) ?? []
+        }
+        options={
+          manualItemsField.value?.map((item) => ({
+            label: item,
+            value: item,
+          })) ?? []
+        }
+        classNames={{ container: () => 'w-full h-9' }}
+        onChange={(newValue) => {
+          // set the new collection items
+          const newItems = newValue.map((item) => item.value)
+          manualItemsHelpers.setValue(newItems)
+
+          // update the answer collection state for UI parsing
+          setAnswerCollectionEntries(
+            newItems.map((item, index) => ({
+              id: index,
+              value: item,
+            }))
+          )
+        }}
path: apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add validation for empty or whitespace-only items.**

The current implementation doesn't validate whether newly created items are empty or contain only whitespace. This could lead to invalid data being stored.

```diff
onChange={(newValue) => {
  // set the new collection items
- const newItems = newValue.map((item) => item.value)
+ const newItems = newValue
+   .map((item) => item.value)
+   .filter((item) => item.trim().length > 0)
  manualItemsHelpers.setValue(newItems)

  // update the answer collection state for UI parsing
  setAnswerCollectionEntries(
    newItems.map((item, index) => ({
      id: index,
      value: item,
    }))
  )
}
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
around lines 81 to 93, the onChange handler does not validate new items for
empty or whitespace-only values. To fix this, add a validation step before
setting newItems to filter out any items that are empty strings or contain only
whitespace. Use a trim check on each item and only include those with non-empty
trimmed values in newItems and the subsequent setAnswerCollectionEntries call.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T16:07:09Z
author_association: NONE
diff_hunk: @@ -0,0 +1,116 @@
+import { FormLabel, UserNotification } from '@uzh-bf/design-system'
+import { useField } from 'formik'
+import { useTranslations } from 'next-intl'
+import { Dispatch, SetStateAction } from 'react'
+import Creatable from 'react-select/creatable'
+import useSelectedNewItems from './useSelectedNewItems'
+
+function CaseStudyManualItemCreation({
+  disabled,
+  itemSelectionMode,
+  setItemSelectionMode,
+  setAnswerCollectionEntries,
+  setSelectedItems,
+}: {
+  disabled: boolean
+  itemSelectionMode: 'existing' | 'new'
+  setItemSelectionMode: (newValue: 'existing' | 'new') => void
+  setAnswerCollectionEntries: Dispatch<
+    SetStateAction<{ id: number; value: string }[]>
+  >
+  setSelectedItems: Dispatch<SetStateAction<{ id: number; name: string }[]>>
+}) {
+  const t = useTranslations()
+  const [manualItemsField, ____, manualItemsHelpers] = useField<string[]>(
+    'options.manuallyCreatedItems'
+  )
+
+  // update the selected items state whenever the manually created items change
+  useSelectedNewItems({
+    createdItems: manualItemsField.value ?? [],
+    itemSelectionMode,
+    setSelectedItems,
+  })
+
+  return (
+    <div>
+      <UserNotification type="info">
+        {t.rich('manage.elements.enterItemsManuallyExplanation', {
+          b: (text) => <b>{text}</b>,
+          button: (text) => (
+            <span
+              onClick={() => {
+                // reset the selected items
+                setAnswerCollectionEntries([])
+
+                // switch to the selection mode for existing answer collections
+                setItemSelectionMode('existing')
+              }}
+              className="cursor-pointer hover:underline"
+            >
+              {text}
+            </span>
+          ),
+        })}
+      </UserNotification>
+
+      <FormLabel
+        required
+        label={t('shared.generic.caseStudyItems')}
+        tooltip={t('manage.elements.newCaseStudyItemsTooltip')}
+        labelType="small"
+        className={{ label: 'mt-2' }}
+      />
+      <Creatable
+        isClearable
+        isMulti
+        isDisabled={disabled}
+        value={
+          manualItemsField.value?.map((item) => ({
+            label: item,
+            value: item,
+          })) ?? []
+        }
+        options={
+          manualItemsField.value?.map((item) => ({
+            label: item,
+            value: item,
+          })) ?? []
+        }
+        classNames={{ container: () => 'w-full h-9' }}
+        onChange={(newValue) => {
+          // set the new collection items
+          const newItems = newValue.map((item) => item.value)
+          manualItemsHelpers.setValue(newItems)
+
+          // update the answer collection state for UI parsing
+          setAnswerCollectionEntries(
+            newItems.map((item, index) => ({
+              id: index,
+              value: item,
+            }))
+          )
+        }}
+        onCreateOption={(newTag) => {
+          // add the new tag to the list of new collection items, if it does not exist already
+          if (!manualItemsField.value?.includes(newTag)) {
+            manualItemsHelpers.setValue([
+              ...(manualItemsField.value ?? []),
+              newTag,
+            ])
+
+            // update the answer collection state for UI parsing
+            setAnswerCollectionEntries((prev) => [
+              ...prev,
+              { id: prev.length, value: newTag },
+            ])
+          }
+        }}
path: apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Validate new items before adding them.**

Similar to the onChange handler, the onCreateOption function should validate that new tags aren't empty or whitespace-only before adding them.

```diff
onCreateOption={(newTag) => {
+ // Trim the new tag and check if it's not empty
+ const trimmedTag = newTag.trim()
+ if (trimmedTag.length === 0) return
+
  // add the new tag to the list of new collection items, if it does not exist already
- if (!manualItemsField.value?.includes(newTag)) {
+ if (!manualItemsField.value?.includes(trimmedTag)) {
    manualItemsHelpers.setValue([
      ...(manualItemsField.value ?? []),
-     newTag,
+     trimmedTag,
    ])

    // update the answer collection state for UI parsing
    setAnswerCollectionEntries((prev) => [
      ...prev,
-     { id: prev.length, value: newTag },
+     { id: prev.length, value: trimmedTag },
    ])
  }
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        onCreateOption={(newTag) => {
          // Trim the new tag and check if it's not empty
          const trimmedTag = newTag.trim()
          if (trimmedTag.length === 0) return

          // add the new tag to the list of new collection items, if it does not exist already
          if (!manualItemsField.value?.includes(trimmedTag)) {
            manualItemsHelpers.setValue([
              ...(manualItemsField.value ?? []),
              trimmedTag,
            ])

            // update the answer collection state for UI parsing
            setAnswerCollectionEntries((prev) => [
              ...prev,
              { id: prev.length, value: trimmedTag },
            ])
          }
        }}
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
around lines 94 to 108, the onCreateOption function adds new tags without
validating if they are empty or contain only whitespace. To fix this, add a
check to ensure newTag is not empty or whitespace-only before proceeding with
adding it to manualItemsField.value and updating the answer collection state.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T16:07:09Z
author_association: NONE
diff_hunk: @@ -0,0 +1,30 @@
+import { Dispatch, SetStateAction, useMemo } from 'react'
+import { ElementFormTypesCaseStudy } from '../types'
+
+function useSelectedNewItems({
+  createdItems,
+  itemSelectionMode,
+  setSelectedItems,
+}: {
+  createdItems: ElementFormTypesCaseStudy['options']['manuallyCreatedItems']
+  itemSelectionMode: 'existing' | 'new'
+  setSelectedItems?: Dispatch<SetStateAction<{ id: number; name: string }[]>>
+}) {
+  return useMemo(() => {
+    if (
+      typeof createdItems === 'undefined' ||
+      createdItems.length === 0 ||
+      typeof itemSelectionMode === 'undefined' ||
+      itemSelectionMode === 'existing'
+    ) {
+      return []
+    }
+
+    setSelectedItems?.(
+      createdItems.map((item) => ({ id: item.id, name: item.value }))
+    )
+    return null
+  }, [createdItems, itemSelectionMode, setSelectedItems])
+}
+
+export default useSelectedNewItems
path: apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider refactoring this hook to follow React best practices.**

The hook uses `useMemo` for a side effect (calling `setSelectedItems`), which is not the intended use of this hook. `useMemo` is designed for memoizing computed values, not for executing side effects.


Consider refactoring to use `useEffect` for side effects:

```diff
-import { Dispatch, SetStateAction, useMemo } from 'react'
+import { Dispatch, SetStateAction, useEffect, useMemo } from 'react'
 import { ElementFormTypesCaseStudy } from '../types'

 function useSelectedNewItems({
   createdItems,
   itemSelectionMode,
   setSelectedItems,
 }: {
   createdItems: ElementFormTypesCaseStudy['options']['manuallyCreatedItems']
   itemSelectionMode: 'existing' | 'new'
   setSelectedItems?: Dispatch<SetStateAction<{ id: number; name: string }[]>>
 }) {
-  return useMemo(() => {
+  const shouldSyncItems = 
+    createdItems?.length > 0 && 
+    itemSelectionMode === 'new' && 
+    setSelectedItems !== undefined
+
+  useEffect(() => {
     if (
-      typeof createdItems === 'undefined' ||
-      createdItems.length === 0 ||
-      typeof itemSelectionMode === 'undefined' ||
-      itemSelectionMode === 'existing'
+      shouldSyncItems
     ) {
-      return []
+      setSelectedItems(
+        createdItems.map((item) => ({ id: item.id, name: item.value }))
+      )
     }
+  }, [createdItems, itemSelectionMode, setSelectedItems, shouldSyncItems])

-    setSelectedItems?.(
-      createdItems.map((item) => ({ id: item.id, name: item.value }))
-    )
-    return null
-  }, [createdItems, itemSelectionMode, setSelectedItems])
+  return useMemo(() => 
+    shouldSyncItems 
+      ? createdItems.map((item) => ({ id: item.id, name: item.value }))
+      : []
+  , [createdItems, shouldSyncItems])
 }

 export default useSelectedNewItems
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
import { Dispatch, SetStateAction, useEffect, useMemo } from 'react'
import { ElementFormTypesCaseStudy } from '../types'

function useSelectedNewItems({
  createdItems,
  itemSelectionMode,
  setSelectedItems,
}: {
  createdItems: ElementFormTypesCaseStudy['options']['manuallyCreatedItems']
  itemSelectionMode: 'existing' | 'new'
  setSelectedItems?: Dispatch<SetStateAction<{ id: number; name: string }>[]>
}) {
  const shouldSyncItems =
    createdItems?.length > 0 &&
    itemSelectionMode === 'new' &&
    setSelectedItems !== undefined

  useEffect(() => {
    if (shouldSyncItems) {
      setSelectedItems(
        createdItems.map((item) => ({ id: item.id, name: item.value }))
      )
    }
  }, [createdItems, itemSelectionMode, setSelectedItems, shouldSyncItems])

  return useMemo(
    () =>
      shouldSyncItems
        ? createdItems.map((item) => ({ id: item.id, name: item.value }))
        : [],
    [createdItems, shouldSyncItems]
  )
}

export default useSelectedNewItems
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts
lines 1 to 30, the hook incorrectly uses useMemo to perform a side effect by
calling setSelectedItems, which violates React best practices. Refactor the hook
to use useEffect instead of useMemo for the side effect of updating selected
items. Move the logic that calls setSelectedItems inside a useEffect hook with
the same dependencies, and return the selected items value directly or adjust
the hook's return accordingly.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T16:55:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,162 @@
+import { FormLabel, UserNotification } from '@uzh-bf/design-system'
+import { useField } from 'formik'
+import { useTranslations } from 'next-intl'
+import { Dispatch, SetStateAction } from 'react'
+import Creatable from 'react-select/creatable'
+import { ElementFormTypesCaseStudy } from '../types'
+import useSelectedNewItems from './useSelectedNewItems'
+
+function CaseStudyManualItemCreation({
+  disabled,
+  itemSelectionMode,
+  setItemSelectionMode,
+  setAnswerCollectionEntries,
+  setSelectedItems,
+}: {
+  disabled: boolean
+  itemSelectionMode: 'existing' | 'new'
+  setItemSelectionMode: (newValue: 'existing' | 'new') => void
+  setAnswerCollectionEntries: Dispatch<
+    SetStateAction<{ id: number; value: string }[]>
+  >
+  setSelectedItems: Dispatch<SetStateAction<{ id: number; name: string }[]>>
+}) {
+  const t = useTranslations()
+  const [manualItemsField, ____, manualItemsHelpers] = useField<
+    ElementFormTypesCaseStudy['options']['manuallyCreatedItems']
+  >('options.manuallyCreatedItems')
+  const [casesField, ___, casesHelpers] =
+    useField<ElementFormTypesCaseStudy['options']['cases']>('options.cases')
+
+  // update the selected items state whenever the manually created items change
+  useSelectedNewItems({
+    createdItems: manualItemsField.value ?? [],
+    itemSelectionMode,
+    setSelectedItems,
+  })
+
+  return (
+    <div>
+      <UserNotification type="info">
+        {t.rich('manage.elements.enterItemsManuallyExplanation', {
+          b: (text) => <b>{text}</b>,
+          button: (text) => (
+            <span
+              onClick={() => {
+                // reset the selected items
+                setAnswerCollectionEntries([])
+
+                // switch to the selection mode for existing answer collections
+                setItemSelectionMode('existing')
+              }}
+              className="cursor-pointer hover:underline"
+            >
+              {text}
+            </span>
+          ),
+        })}
+      </UserNotification>
+
+      <FormLabel
+        required
+        label={t('shared.generic.caseStudyItems')}
+        tooltip={t('manage.elements.newCaseStudyItemsTooltip')}
+        labelType="small"
+        className={{ label: 'mt-2' }}
+      />
+      <Creatable
+        isClearable
+        isMulti
+        isDisabled={disabled}
+        value={
+          manualItemsField.value?.map((item) => ({
+            label: item.value,
+            value: item.id,
+          })) ?? []
+        }
+        options={
+          manualItemsField.value?.map((item) => ({
+            label: item.value,
+            value: item.id,
+          })) ?? []
+        }
+        classNames={{ container: () => 'w-full h-9' }}
+        onChange={(newValue) => {
+          // set the new collection items
+          const prevItems = manualItemsField.value ?? []
+          const newItems = newValue.map((item) => ({
+            id: item.value,
+            value: item.label,
+          }))
+          manualItemsHelpers.setValue(newItems)
+
+          // check if an item has been removed and conditionally remove the solutions for this item
+          if (newItems.length < prevItems.length) {
+            // identify the removed item
+            const removedItem = prevItems.find(
+              (prevItem) =>
+                !newItems.map((item) => item.id).includes(prevItem.id)
+            )
+
+            // if an item has been removed, remove the corresponding key from the case solutions
+            if (removedItem) {
+              const newCases = casesField.value?.map((caseItem) => {
+                // if no solutions are set, skip this case
+                if (!('solutions' in caseItem) || !caseItem.solutions) {
+                  return caseItem
+                }
+
+                // filter out all solution entries for the removed item
+                const newSolutions = Object.fromEntries(
+                  Object.entries(caseItem.solutions).filter(
+                    ([itemIdString]) =>
+                      itemIdString !== `itemId-${removedItem.id}`
+                  )
+                )
+
+                return {
+                  ...caseItem,
+                  solutions: newSolutions,
+                }
+              })
+
+              // update the cases field
+              casesHelpers.setValue(newCases)
+            }
+          }
+
+          // update the answer collection state for correct validation
+          setAnswerCollectionEntries(
+            newItems.map((item) => ({
+              id: item.id,
+              value: item.value,
+            }))
+          )
+        }}
+        onCreateOption={(newValue) => {
+          // add the new tag to the list of new collection items, if it does not exist already
+          if (
+            !manualItemsField.value?.some(
+              (existingItem) => existingItem.value === newValue
+            )
+          ) {
+            manualItemsHelpers.setValue([
+              ...(manualItemsField.value ?? []),
+              { id: Math.floor(Math.random() * 1000000 + 1), value: newValue },
path: apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Replace random ID generation with a more reliable approach.**

Using `Math.random()` for generating IDs can potentially lead to duplicate IDs. Consider using sequential IDs or a more robust ID generation method.

```diff
- { id: Math.floor(Math.random() * 1000000 + 1), value: newValue },
+ { id: (manualItemsField.value?.length || 0) + 1, value: newValue },
```

Alternatively, you could use a more reliable unique ID generation library like `uuid` if available in your project.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
// ...
manualItemsHelpers.setValue([
  ...(manualItemsField.value ?? []),
- { id: Math.floor(Math.random() * 1000000 + 1), value: newValue },
+ { id: (manualItemsField.value?.length || 0) + 1, value: newValue },
])
// ...
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
at line 145, replace the current random ID generation using Math.random() with a
more reliable method to avoid potential duplicate IDs. You can implement
sequential ID generation by maintaining a counter or use a unique ID generation
library like uuid if it is available in the project. Update the code to generate
IDs using one of these approaches instead of Math.random().
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T17:03:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,162 @@
+import { FormLabel, UserNotification } from '@uzh-bf/design-system'
+import { useField } from 'formik'
+import { useTranslations } from 'next-intl'
+import { Dispatch, SetStateAction } from 'react'
+import Creatable from 'react-select/creatable'
+import { ElementFormTypesCaseStudy } from '../types'
+import useSelectedNewItems from './useSelectedNewItems'
+
+function CaseStudyManualItemCreation({
+  disabled,
+  itemSelectionMode,
+  setItemSelectionMode,
+  setAnswerCollectionEntries,
+  setSelectedItems,
+}: {
+  disabled: boolean
+  itemSelectionMode: 'existing' | 'new'
+  setItemSelectionMode: (newValue: 'existing' | 'new') => void
+  setAnswerCollectionEntries: Dispatch<
+    SetStateAction<{ id: number; value: string }[]>
+  >
+  setSelectedItems: Dispatch<SetStateAction<{ id: number; name: string }[]>>
+}) {
+  const t = useTranslations()
+  const [manualItemsField, ____, manualItemsHelpers] = useField<
+    ElementFormTypesCaseStudy['options']['manuallyCreatedItems']
+  >('options.manuallyCreatedItems')
+  const [casesField, ___, casesHelpers] =
+    useField<ElementFormTypesCaseStudy['options']['cases']>('options.cases')
+
+  // update the selected items state whenever the manually created items change
+  useSelectedNewItems({
+    createdItems: manualItemsField.value ?? [],
+    itemSelectionMode,
+    setSelectedItems,
+  })
+
+  return (
+    <div>
+      <UserNotification type="info">
+        {t.rich('manage.elements.enterItemsManuallyExplanation', {
+          b: (text) => <b>{text}</b>,
+          button: (text) => (
+            <span
+              onClick={() => {
+                // reset the selected items
+                setAnswerCollectionEntries([])
+
+                // switch to the selection mode for existing answer collections
+                setItemSelectionMode('existing')
+              }}
+              className="cursor-pointer hover:underline"
+            >
+              {text}
+            </span>
+          ),
+        })}
+      </UserNotification>
+
+      <FormLabel
+        required
+        label={t('shared.generic.caseStudyItems')}
+        tooltip={t('manage.elements.newCaseStudyItemsTooltip')}
+        labelType="small"
+        className={{ label: 'mt-2' }}
+      />
+      <Creatable
+        isClearable
+        isMulti
+        isDisabled={disabled}
+        value={
+          manualItemsField.value?.map((item) => ({
+            label: item.value,
+            value: item.id,
+          })) ?? []
+        }
+        options={
+          manualItemsField.value?.map((item) => ({
+            label: item.value,
+            value: item.id,
+          })) ?? []
+        }
+        classNames={{ container: () => 'w-full h-9' }}
+        onChange={(newValue) => {
+          // set the new collection items
+          const prevItems = manualItemsField.value ?? []
+          const newItems = newValue.map((item) => ({
+            id: item.value,
+            value: item.label,
+          }))
+          manualItemsHelpers.setValue(newItems)
+
+          // check if an item has been removed and conditionally remove the solutions for this item
+          if (newItems.length < prevItems.length) {
+            // identify the removed item
+            const removedItem = prevItems.find(
+              (prevItem) =>
+                !newItems.map((item) => item.id).includes(prevItem.id)
+            )
+
+            // if an item has been removed, remove the corresponding key from the case solutions
+            if (removedItem) {
+              const newCases = casesField.value?.map((caseItem) => {
+                // if no solutions are set, skip this case
+                if (!('solutions' in caseItem) || !caseItem.solutions) {
+                  return caseItem
+                }
+
+                // filter out all solution entries for the removed item
+                const newSolutions = Object.fromEntries(
+                  Object.entries(caseItem.solutions).filter(
+                    ([itemIdString]) =>
+                      itemIdString !== `itemId-${removedItem.id}`
+                  )
+                )
+
+                return {
+                  ...caseItem,
+                  solutions: newSolutions,
+                }
+              })
+
+              // update the cases field
+              casesHelpers.setValue(newCases)
+            }
+          }
path: apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider adding a confirmation before removing items with associated solutions.**

When an item is removed that has associated solutions in case studies, those solutions are automatically removed without user confirmation. Consider adding a confirmation dialog when removing an item that has solutions attached to it.

```jsx
// Pseudocode for the confirmation flow
if (removedItem && hasSolutionsForItem(removedItem.id, casesField.value)) {
  const confirmRemoval = window.confirm(
    t('manage.elements.confirmRemoveItemWithSolutions')
  );
  
  if (!confirmRemoval) {
    // Restore the item to the list
    manualItemsHelpers.setValue(prevItems);
    return;
  }
}
```


Let's check if this component has access to any confirmation dialog utilities elsewhere in the codebase:

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for confirmation dialog/modal components
echo "Searching for confirmation dialog components..."
rg -l "confirm(ation)?.*dialog|confirm(ation)?.*modal" --type=tsx
```

Length of output: 200

---

🏁 Script executed:

```shell
#!/bin/bash
echo "Searching for existing confirmation or modal components across TS and TSX files..."

rg -l -g "*.tsx" -i "confirm(ation)?"
rg -l -g "*.tsx" -i "dialog"
rg -l -g "*.tsx" -i "modal"

rg -l -g "*.ts" -i "confirm(ation)?"
rg -l -g "*.ts" -i "dialog"
rg -l -g "*.ts" -i "modal"
```

Length of output: 21148

---

🏁 Script executed:

```shell
#!/bin/bash
echo "=== ConfirmationItem.tsx ==="
sed -n '1,200p' apps/frontend-manage/src/components/common/ConfirmationItem.tsx || echo "File not found."

echo "=== CaseStudyManualItemCreation.tsx (top) ==="
sed -n '1,200p' apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx || echo "File not found."
```

Length of output: 7643

---



</details>

**Add a confirmation step before dropping associated case-study solutions**

Before automatically stripping out solutions for a removed manual item, prompt the user to confirm—especially if that item had answers in any case. You can follow the pattern of our other “deletion” modals (e.g. `ElementDeletionModal.tsx`) or build a small inline modal/confirmation component.

Key changes in `CaseStudyManualItemCreation.tsx` (around the `onChange` handler’s removal block):

- Detect if the `removedItem` actually had solutions:
  ```ts
  const hasAssociatedSolutions = casesField.value?.some(
    caseItem => caseItem.solutions?.[`itemId-${removedItem.id}`]
  );
  ```
- If true, open a confirmation modal (or inline prompt):
  ```ts
  if (removedItem && hasAssociatedSolutions) {
    openConfirmationModal({
      title: t('manage.elements.confirmRemoveItemTitle'),
      description: t('manage.elements.confirmRemoveItemWithSolutions'),
      onConfirm: () => {
        // proceed with filtering out solutions + casesHelpers.setValue(...)
      },
      onCancel: () => {
        // revert manual items and abort
        manualItemsHelpers.setValue(prevItems);
      },
    });
    return;
  }
  ```
- Otherwise, fall back to the existing solution-removal logic.

You’ll need to:
- Add translation keys for the modal copy (e.g. `confirmRemoveItemTitle`, `confirmRemoveItemWithSolutions`).
- Import and reuse one of our existing modal components (e.g. `ElementDeletionModal.tsx`) or build a small wrapper around the design-system’s modal.
- Wire up the `onCancel` to restore `manualItemsHelpers.setValue(prevItems)` and skip the rest of the removal logic.

This ensures users don’t accidentally lose their case-study answers when pruning manual items.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx
around lines 94 to 126, add a confirmation step before removing an item that has
associated solutions in case studies. Detect if the removed item has solutions
by checking casesField.value for entries keyed by the removed item's id. If such
solutions exist, open a confirmation modal (e.g., reuse
ElementDeletionModal.tsx) with appropriate translation keys for title and
description. On confirm, proceed with removing the solutions and updating
casesField; on cancel, restore the previous manual items list using
manualItemsHelpers.setValue(prevItems) and abort removal. This prevents
accidental loss of case-study answers when deleting items.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T17:03:19Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-21T16:04:31Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: rate limited by coderabbit.ai -->

> [!WARNING]
> ## Rate limit exceeded
> 
> @sjschlapbach has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait **13 minutes and 48 seconds** before requesting another review.
> 
> <details>
> <summary>⌛ How to resolve this issue?</summary>
> 
> After the wait time has elapsed, a review can be triggered using the `@coderabbitai review` command as a PR comment. Alternatively, push new commits to this PR.
> 
> We recommend that you space out your commits to avoid hitting the rate limit.
> 
> </details>
> 
> 
> <details>
> <summary>🚦 How do rate limits work?</summary>
> 
> CodeRabbit enforces hourly rate limits for each developer per organization.
> 
> Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
> 
> Please see our [FAQ](https://docs.coderabbit.ai/faq) for further information.
> 
> </details>
> 
> <details>
> <summary>📥 Commits</summary>
> 
> Reviewing files that changed from the base of the PR and between f230799eaec164b00f69c1f4dcc1c48c126cfb76 and 460b487a55f554226ef7fd06598bd3eb16f51bec.
> 
> </details>
> 
> <details>
> <summary>📒 Files selected for processing (1)</summary>
> 
> * `apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx` (1 hunks)
> 
> </details>

<!-- end of auto-generated comment: rate limited by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

This change introduces a manual entry workflow for case study items in the frontend, allowing users to define new items directly within the form rather than only selecting from existing answer collections. It adds new components, hooks, and validation logic to support this feature, updates related type definitions, and enhances localization for the new UI paths.

## Changes

| File(s) | Change Summary |
|---------|---------------|
| apps/frontend-manage/src/components/activities/templates/TemplateNewElementModal.tsx | Removed `failureToast` state and related props; updated `onSubmitElement` to return a boolean. |
| apps/frontend-manage/src/components/questions/manipulation/ElementEditForm.tsx | Internalized failure toast state; updated `onSubmitElement` to return a boolean; added `creationMode` prop for `CaseStudyOptions`. |
| apps/frontend-manage/src/components/questions/manipulation/ElementEditModal.tsx | Refactored error handling to return booleans; added support for manual item creation in case studies; integrated answer collection creation for new items. |
| apps/frontend-manage/src/components/questions/manipulation/ElementFormErrors.tsx | Added error rendering for `manuallyCreatedItems` in case study options. |
| apps/frontend-manage/src/components/questions/manipulation/ElementTypeMonitor.tsx | Expanded `useEffect` dependency array for more robust effect triggering. |
| apps/frontend-manage/src/components/questions/manipulation/helpers.ts | Added non-null assertion for `answerCollection` parsing in `prepareCaseStudyArgs`. |
| apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCollectionSelection.tsx | Added support for manual entry mode, new props for selection mode, and UI for switching modes. |
| apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyManualItemCreation.tsx | **New component** for manual creation and management of case study items. |
| apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyOptions.tsx | Added `creationMode` prop; conditionally renders manual item creation or collection selection UI. |
| apps/frontend-manage/src/components/questions/manipulation/options/useSelectedAnswerEntry.ts | Updated hook to respect selection mode and return empty array in manual mode. |
| apps/frontend-manage/src/components/questions/manipulation/options/useSelectedNewItems.ts | **New hook** for synchronizing selected items state when manually creating items. |
| apps/frontend-manage/src/components/questions/manipulation/types.ts | Updated types for case study options: added `itemSelectionMode`, made fields optional, documented key semantics. |
| apps/frontend-manage/src/components/questions/manipulation/useArtificialElementInstance.ts | Adjusted logic to construct items array based on selection mode and manual items. |
| apps/frontend-manage/src/components/questions/manipulation/useElementFormInitialValues.ts | Set initial values for new selection mode and manual items in case studies. |
| apps/frontend-manage/src/components/questions/manipulation/useValidationSchema.ts | Extended validation schema for manual item entry, conditional requirements, and dynamic solution validation. |
| apps/frontend-manage/src/pages/index.tsx | Minor whitespace cleanup. |
| packages/graphql/src/schema/resource.ts | Minor whitespace formatting. |
| packages/graphql/src/services/resources.ts | Updated `createAnswerCollection` to return full entries instead of just counts. |
| packages/i18n/messages/de.ts | Added German localization for manual case study item entry and related UI. |
| packages/i18n/messages/en.ts | Added English localization for manual case study item entry and related UI. |
| apps/frontend-manage/src/components/questions/manipulation/options/CaseStudyCriterionModeMonitor.tsx | Fixed typo in comment ("criterio" to "criterion"). |
| apps/frontend-manage/src/components/questions/manipulation/options/SelectionOptions.tsx | Added explicit `itemSelectionMode: 'existing'` argument to `useSelectedAnswerEntry` hook call. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant Form
    participant ManualItemComponent
    participant Backend

    User->>Form: Open case study element creation
    Form->>User: Show option to select or manually enter items
    User->>Form: Select "Enter items manually"
    Form->>ManualItemComponent: Render manual entry UI
    User->>ManualItemComponent: Add/edit items
    ManualItemComponent->>Form: Update form state with new items
    User->>Form: Submit form
    Form->>Backend: Create answer collection with manual items
    Backend-->>Form: Return new collection ID and entries
    Form->>Backend: Create case study element with reference to new collection
    Backend-->>Form: Return success/failure
    Form->>User: Show success or error message
```

## Possibly related PRs

- uzh-bf/klicker-uzh#4477: The main PR and the retrieved PR both modify the case study element type and its related UI components, including the element modal and options, indicating a direct continuation and extension of the same feature implementation.
- uzh-bf/klicker-uzh#4481: The main PR and the retrieved PR both modify the `ElementEditModal.tsx` file to enhance handling of case study elements, including adding or refining the `onSubmit` mutation logic and conditional rendering of `CaseStudyOptions`, showing a direct and strong connection at the code level.  
- uzh-bf/klicker-uzh#4482: The main PR focuses on internal refactoring and feature additions related to case study element creation and manual item management in the frontend React components, while the retrieved PR adds Cypress end-to-end tests for creation, editing, and validation of case study elements; thus, they are related by the feature domain but do not overlap at the code level or modify the same functions or components directly.  

## Suggested labels

`size:L`

## Suggested reviewers

- rschlaefli

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=uzh-bf/klicker-uzh&utm_content=4704):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-21T17:07:42Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=uzh-bf_klicker-uzh&pullRequest=4704) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed.svg '') [B Reliability Rating on New Code](https://sonarcloud.io/dashboard?id=uzh-bf_klicker-uzh&pullRequest=4704) (required ≥ A)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=uzh-bf_klicker-uzh&pullRequest=4704)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb.svg '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint.svg '') [SonarQube for IDE](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


author_association: NONE
user: login: cypress[bot]
created_at: 2025-05-21T17:09:44Z
body: 
<h3>
  <a href="https://cloud.cypress.io/projects/y436dx?utm_source=github&utm_medium=PASSED&utm_campaign=project">klicker-uzh</a>&nbsp;
  <img src="https://assets.cypress.io/github-pr-comment-icons/chevron.svg" alt="" height="14" width="14" />&nbsp;
  <a href="https://cloud.cypress.io/projects/y436dx/runs/5234/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">Run #5234</a>
</h3>

<details>
  <summary>
    <strong>Run Properties:</strong>&nbsp;
    <code><img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="status check passed" height="10" width="10" />&nbsp;Passed&nbsp;<a href="https://cloud.cypress.io/projects/y436dx/runs/5234/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">#5234</a></code> &nbsp;&bull;&nbsp;
    <code><img src="https://assets.cypress.io/github-pr-comment-icons/git-commit.svg" alt="git commit" width="10" height="10" /> <a href="https://github.com/uzh-bf/klicker-uzh/commit/eb61a02af57d1784779e8c57b75e3609b039bf96">eb61a02af5 <span style="cursor:help;" title="Your CI provider generated this commit by merging this branch into the target branch.">ℹ️</span></a>: Merge 460b487a55f554226ef7fd06598bd3eb16f51bec into b1385627ef3001c6dab95e749027...</code>
  </summary><br />

  <table width="744">
    <tbody>
      <tr />
      <tr height="48">
        <td width="224">
          <strong>Project</strong>
        </td>
        <td width="520">
          <code><a href="https://cloud.cypress.io/projects/y436dx?utm_source=github&utm_medium=PASSED&utm_campaign=project">klicker-uzh</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td width="224">
          <strong>Branch Review</strong>
        </th>
        <td>
          <code><a href="https://cloud.cypress.io/projects/y436dx/branches/inline-acs-case-study/review?utm_source=github&utm_medium=PASSED&utm_campaign=review%20changes&changeRequest=4704">inline-acs-case-study</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Run status</strong></td>
        <td>
          <code><img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="status check passed" height="10" width="10" />&nbsp;Passed&nbsp;<a href="https://cloud.cypress.io/projects/y436dx/runs/5234/overview?utm_source=github&utm_medium=PASSED&utm_campaign=run%20number">#5234</a></code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Run duration</strong></td>
        <td><code>59m 24s</code></td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Commit</strong></td>
        <td>
          <code><img src="https://assets.cypress.io/github-pr-comment-icons/git-commit.svg" alt="git commit" width="10" height="10" /> <a href="https://github.com/uzh-bf/klicker-uzh/commit/eb61a02af57d1784779e8c57b75e3609b039bf96">eb61a02af5 <span style="cursor:help;" title="Your CI provider generated this commit by merging this branch into the target branch.">ℹ️</span></a>: Merge 460b487a55f554226ef7fd06598bd3eb16f51bec into b1385627ef3001c6dab95e749027...</code>
        </td>
      </tr>
      <tr />
      <tr height="48">
        <td><strong>Committer</strong></td>
        <td><code>Julius Schlapbach</code></td>
      </tr>
    </tbody>
    <tfoot>
      <tr />
      <tr height="48">
        <td colspan="2">
          <a href="https://cloud.cypress.io/projects/y436dx/runs/5234/properties?utm_source=github&utm_medium=PASSED&utm_campaign=properties">View all properties for this run ↗︎</a>
        </td>
      </tr>
    </tfoot>
  </table>
  <hr />
</details>

<table width="744">
  <thead>
    <tr />
    <tr height="48">
      <th colspan="2" align="left" width="744">Test results</th>
    </tr>
  </thead>
  <tbody>
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/failed.svg" alt="Tests that failed" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/5234/overview?reviewViewBy=FAILED&utm_source=github&utm_medium=PASSED&utm_campaign=failed%20tests">
          <strong>Failures</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/flaky-v3.svg" alt="Tests that were flaky" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/5234/overview?reviewViewBy=FLAKY&utm_source=github&utm_medium=PASSED&utm_campaign=flaky%20tests">
          <strong>Flaky</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/pending.svg" alt="Tests that did not run due to a developer annotating a test with .skip" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/5234/test-results?utm_source=github&utm_medium=PASSED&utm_campaign=pending%20tests&statuses=%5B%7B%22value%22%3A%22PENDING%22%2C%22label%22%3A%22PENDING%22%7D%5D">
          <strong>Pending</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/skipped-v3.svg" alt="Tests that did not run due to a failure in a mocha hook" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/5234/test-results?utm_source=github&utm_medium=PASSED&utm_campaign=skipped%20tests&statuses=%5B%7B%22value%22%3A%22SKIPPED%22%2C%22label%22%3A%22SKIPPED%22%7D%5D">
          <strong>Skipped</strong>
        </a>
      </td>
      <td>
        <code><strong>0</strong></code>
      </td>
    </tr>
    <tr />
    <tr height="48">
      <td width="224">
        <img src="https://assets.cypress.io/github-pr-comment-icons/passed.svg" alt="Tests that passed" width="10" height="10" />&nbsp;
        <a href="https://cloud.cypress.io/projects/y436dx/runs/5234/overview?utm_source=github&utm_medium=PASSED&utm_campaign=passed%20tests">
          <strong>Passing</strong>
        </a>
      </td>
      <td>
        <code><strong>795</strong></code>
      </td>
    </tr><tfoot>
    <tr><td colspan="2"><b>⚠️ You've recorded test results over your free plan limit.<br /><a href="https://cloud.cypress.io/organizations/1a5729a2-8fed-493d-8ac1-0fcf1628e4fa/billing?utm_source=github&utm_medium=PASSED&utm_campaign=upgrade%20plan">Upgrade your plan to view test results</a>.</b></td></tr>
    <tr />
    <tr height="48"><td colspan="2"><a href="https://cloud.cypress.io/projects/y436dx/branches/inline-acs-case-study/review?utm_source=github&utm_medium=PASSED&utm_campaign=review%20changes&changeRequest=4704">View all changes introduced in this branch ↗︎</a></td></tr></tfoot>
  </tbody>
</table>






author_association: NONE