Skip to content

feat: add management components (creation and editing) for new polls activity#5031

Open
sjschlapbach wants to merge 7 commits intov3-pollsfrom
poll-management
Open

feat: add management components (creation and editing) for new polls activity#5031
sjschlapbach wants to merge 7 commits intov3-pollsfrom
poll-management

Conversation

@sjschlapbach
Copy link
Copy Markdown
Member

@sjschlapbach sjschlapbach commented Mar 14, 2026

Summary by CodeRabbit

  • New Features

    • Adds Polls as a first‑class activity: create/edit/duplicate via a multi‑step Poll wizard, appear in activity lists, filters, selection UIs, and detailed poll view with preview links and icons.
    • Adds UI text and translations for poll creation, templates, and help.
  • Chores

    • Backend and database support for polls and instances, plus integration with permissions, access requests, activity logs, and catalog assignment migrations.

greptile-apps[bot]

This comment was marked as spam.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

Adds a new Poll activity end-to-end: Prisma models and migrations, DB view update, GraphQL types/queries/mutations and services, permission recompute plumbing, frontend creation wizards/UI, and i18n strings; integrates Poll across activity lists, sharing, and element stacks.

Changes

Cohort / File(s) Summary
Prisma: Poll models & migrations
packages/prisma/src/prisma/schema/quiz.prisma, packages/prisma/src/prisma/schema/migrations/20260314220223_polls/migration.sql, packages/prisma/src/prisma/schema/migrations/20260314234850_activity_view_polls/migration.sql
Add Poll and PollInstance models; add pollId columns, indexes, unique constraints and FKs to multiple tables; update unified UserActivities view to include POLL.
Prisma: Element/User/Sharing updates
packages/prisma/src/prisma/schema/element.prisma, packages/prisma/src/prisma/schema/user.prisma, packages/prisma/src/prisma/schema/sharing.prisma
Add optional poll relations and pollId fields to ElementStack, User, Permission, AccessRequest, DerivedPermission, ActivityTemplate, CatalogCollectionAssignment, ActivityLogEntry; add/adjust unique/index constraints referencing pollId.
Backend: GraphQL schema & services
packages/graphql/src/schema/poll.ts, packages/graphql/src/schema/query.ts, packages/graphql/src/schema/mutation.ts, packages/graphql/src/services/polls.ts, packages/graphql/src/services/activities.ts, packages/graphql/src/services/elements.ts, packages/graphql/src/services/sharing.ts, packages/graphql/src/index.ts
Introduce Poll GraphQL type; add getSinglePoll query and createPoll/editPoll mutations; implement poll services (manipulatePoll, getSinglePoll, changePollName); integrate poll into activityDetails, activity lists, element filtering, and permission checks; register poll schema.
Permissions util
packages/util/src/permissions.ts, packages/util/src/permissions/poll.ts, packages/util/src/permissions/accessRequest.ts
Add poll-specific derived-permission recompute functions and access-request handling; wire pollId through recomputeDerivedPermissions and related upsert/delete/propagation logic.
Frontend: Poll creation UI & wiring
apps/frontend-manage/src/components/activities/creation/poll/..., apps/frontend-manage/src/components/activities/ActivityCreation.tsx, .../WizardLayout.tsx, .../InstanceUpdateOption.tsx, .../DescriptionStep.tsx, .../StackCreationStep.tsx, .../SuspendedCreationButtons.tsx, .../ActivityOverviewFilters.tsx, .../SuspendedActivitySelection.tsx
Add PollWizard and steps (Information, Description, Stack creation), submit handler, Poll form types, UI buttons/icons, activity filters and selection support, and integrate getSinglePoll into ActivityCreation flow.
Types & GraphQL wiring
packages/types/src/index.ts, packages/graphql/src/index.ts, packages/graphql/src/schema/activities.ts
Add ActivityType.POLL enum member; expose polls in CourseActivityList; import/register poll schema module.
i18n
packages/i18n/messages/en.ts, packages/i18n/messages/de.ts
Add translation keys and strings for Poll, Poll template, UI texts, tooltips, creation/edit messages across namespaces.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant GraphQL as API (schema/mutation)
  participant PollService
  participant PrismaDB as Database
  participant Permissions as RecomputeUtil

  Client->>GraphQL: createPoll/editPoll (name, stacks, ...)
  GraphQL->>PollService: manipulatePoll(args, ctx)
  PollService->>PrismaDB: upsert Poll, upsert/delete stacks & elements, manage instances (transactional)
  PollService->>Permissions: recomputeDerivedPermissions({ pollId })
  Permissions->>PrismaDB: upsert/delete DerivedPermission rows, propagate to elements
  PollService->>PrismaDB: delete orphaned elements/stacks (within transaction)
  PollService->>GraphQL: return ActivityInfo / Poll summary
  GraphQL->>Client: mutation result (id, status, previewLink)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • rschlaefli
  • jabbadizzleCode
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: adding management components (creation and editing) for polls. It directly reflects the substantial changes throughout the codebase for poll creation/editing UI, GraphQL mutations, and service layer implementations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 14, 2026
@sjschlapbach sjschlapbach changed the title feat: add management components (creation and editing) for new polls activity [WIP] feat: add management components (creation and editing) for new polls activity Mar 14, 2026
@sjschlapbach sjschlapbach changed the title [WIP] feat: add management components (creation and editing) for new polls activity feat: add management components (creation and editing) for new polls activity Mar 14, 2026
@sjschlapbach sjschlapbach marked this pull request as draft March 14, 2026 20:11
@dosubot dosubot bot added the feature label Mar 14, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/prisma/src/prisma/schema/element.prisma`:
- Around line 166-167: Remove the field-level `@unique` on pollId in the
ElementStack model (the pollId field) so multiple stacks can belong to the same
Poll; add a compound unique constraint on the ElementStack model like
@@unique([type, pollId, order]) following the pattern used by
PracticeQuiz/MicroLearning/GroupActivity; add a POLL member to both the
ElementStackType and ElementInstanceType enums so poll stacks/elements are
typed; ensure references to Poll.stacks and PollInstance.lastAnsweredStackId
remain consistent with the optional pollId relation.

In `@packages/prisma/src/prisma/schema/sharing.prisma`:
- Around line 459-460: The ObjectType enum is missing a POLL value which
prevents ActivityLogEntry.objectType and AuditLogEntry.objectType from
representing poll-related entries even though ActivityLogEntry has pollId;
update the ObjectType enum to include POLL so logs can identify polls, then run
any prisma generate/migrate steps to apply the schema change and ensure
ActivityLogEntry/AuditLogEntry usages continue to compile against the updated
enum.
- Around line 63-64: Add the missing database constraints and enum member for
poll support: for models Permission, AccessRequest, DerivedPermission, and
CatalogCollectionAssignment add the same @@unique and @@index declarations used
for other object types (use the pattern that references pollId together with
actor/subject keys to prevent duplicate poll permissions/access-requests and to
support upsert/lookup), ensure the pollId field in each model is included in
those constraints (matching the naming/column order used elsewhere), and add a
POLL member to the ObjectType enum so poll-related entries can be recorded in
AuditLogEntry and ActivityLogEntry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 99e83b1e-4fa8-4fb6-8ce3-f1f370b3c58a

📥 Commits

Reviewing files that changed from the base of the PR and between 3cecc70 and 99191f0.

📒 Files selected for processing (4)
  • packages/prisma/src/prisma/schema/element.prisma
  • packages/prisma/src/prisma/schema/quiz.prisma
  • packages/prisma/src/prisma/schema/sharing.prisma
  • packages/prisma/src/prisma/schema/user.prisma

Comment on lines +166 to +167
poll Poll? @relation(fields: [pollId], references: [id], onDelete: Cascade, onUpdate: Cascade)
pollId String? @unique @db.Uuid
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check element.prisma for ElementInstanceType enum definition (around line 65)
head -80 packages/prisma/src/prisma/schema/element.prisma | tail -20

Repository: uzh-bf/klicker-uzh

Length of output: 528


🏁 Script executed:

# Check element.prisma for ElementStackType enum definition (around line 148)
sed -n '140,160p' packages/prisma/src/prisma/schema/element.prisma

Repository: uzh-bf/klicker-uzh

Length of output: 503


🏁 Script executed:

# Check element.prisma around lines 166-167 for the poll relation
sed -n '160,175p' packages/prisma/src/prisma/schema/element.prisma

Repository: uzh-bf/klicker-uzh

Length of output: 766


🏁 Script executed:

# Check quiz.prisma for Poll.stacks (around line 165) and PollInstance.lastAnsweredStackId (around line 190)
wc -l packages/prisma/src/prisma/schema/quiz.prisma

Repository: uzh-bf/klicker-uzh

Length of output: 111


🏁 Script executed:

# Get the full quiz.prisma to understand Poll and PollInstance definitions
cat packages/prisma/src/prisma/schema/quiz.prisma

Repository: uzh-bf/klicker-uzh

Length of output: 17520


Remove the @unique constraint from pollId and add POLL enum members.

The @unique constraint on pollId prevents multiple ElementStack records per Poll, but Poll.stacks is a plural relation and PollInstance.lastAnsweredStackId is documented to enable resuming polls across stacks. This breaks the intended functionality. Additionally, ElementStackType and ElementInstanceType both lack a POLL member, leaving poll stacks/elements untyped.

The pattern across PracticeQuiz, MicroLearning, and GroupActivity uses compound unique constraints (@@unique([type, <activityId>, order])) rather than field-level @unique constraints. Apply the same approach:

Suggested schema fix
 enum ElementInstanceType {
   LIVE_QUIZ
   PRACTICE_QUIZ
   MICROLEARNING
   GROUP_ACTIVITY
+  POLL
 }
@@
 enum ElementStackType {
   PRACTICE_QUIZ
   MICROLEARNING
   GROUP_ACTIVITY
+  POLL
 }
@@
-  pollId String? `@unique` `@db.Uuid`
+  pollId String? `@db.Uuid`
@@
   @@unique([type, practiceQuizId, order])
   @@unique([type, microLearningId, order])
+  @@unique([type, pollId, order])
   @@unique([type, groupActivityId, order])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/prisma/src/prisma/schema/element.prisma` around lines 166 - 167,
Remove the field-level `@unique` on pollId in the ElementStack model (the pollId
field) so multiple stacks can belong to the same Poll; add a compound unique
constraint on the ElementStack model like @@unique([type, pollId, order])
following the pattern used by PracticeQuiz/MicroLearning/GroupActivity; add a
POLL member to both the ElementStackType and ElementInstanceType enums so poll
stacks/elements are typed; ensure references to Poll.stacks and
PollInstance.lastAnsweredStackId remain consistent with the optional pollId
relation.

Comment on lines +63 to +64
poll Poll? @relation(fields: [pollId], references: [id], onDelete: Cascade, onUpdate: Cascade)
pollId String? @db.Uuid
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the sharing.prisma file
fd -t f "sharing.prisma" packages/prisma/src/prisma/schema/

Repository: uzh-bf/klicker-uzh

Length of output: 110


🏁 Script executed:

# Read the sharing.prisma file to see the current state
cat -n packages/prisma/src/prisma/schema/sharing.prisma

Repository: uzh-bf/klicker-uzh

Length of output: 24913


Add missing constraints for poll sharing to match the pattern used by other object types.

The pollId fields have been added to Permission, AccessRequest, DerivedPermission, and CatalogCollectionAssignment, but the corresponding @@unique and @@index constraint definitions are missing. This allows poll permissions and access requests to be duplicated, breaking the documented invariants and the upsert/lookup operations that depend on these constraints.

Additionally, ObjectType enum (lines 386-396) lacks a POLL member, preventing poll-related activities from being logged in AuditLogEntry and ActivityLogEntry.

Suggested constraint additions
 model Permission {
@@
+    @@unique([pollId, userId])
+    @@unique([pollId, userGroupId])
 }
@@
 model AccessRequest {
@@
+    @@unique([pollId, userId, objectAdminOrOwnerId])
+    @@index([pollId, userId])
+    @@index([pollId, objectAdminOrOwnerId])
 }
@@
 model DerivedPermission {
@@
+    @@unique([pollId, userId])
+    @@index([pollId, permissionLevel])
 }
@@
 model CatalogCollectionAssignment {
@@
+    @@unique([pollId, catalogCollectionId])
 }
@@
 enum ObjectType {
+    POLL
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/prisma/src/prisma/schema/sharing.prisma` around lines 63 - 64, Add
the missing database constraints and enum member for poll support: for models
Permission, AccessRequest, DerivedPermission, and CatalogCollectionAssignment
add the same @@unique and @@index declarations used for other object types (use
the pattern that references pollId together with actor/subject keys to prevent
duplicate poll permissions/access-requests and to support upsert/lookup), ensure
the pollId field in each model is included in those constraints (matching the
naming/column order used elsewhere), and add a POLL member to the ObjectType
enum so poll-related entries can be recorded in AuditLogEntry and
ActivityLogEntry.

Comment on lines +459 to +460
poll Poll? @relation(fields: [pollId], references: [id], onDelete: Cascade, onUpdate: Cascade)
pollId String? @db.Uuid
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat packages/prisma/src/prisma/schema/sharing.prisma

Repository: uzh-bf/klicker-uzh

Length of output: 21581


Add POLL to ObjectType enum to support poll logging.

ActivityLogEntry includes a pollId field, but the ObjectType enum lacks a POLL member. Since ActivityLogEntry.objectType and AuditLogEntry.objectType both reference this enum, poll logs cannot identify themselves without reusing another object type.

Suggested enum addition
 enum ObjectType {
     USER_GROUP
     CATALOG_COLLECTION
     ANSWER_COLLECTION
     ELEMENT
     COURSE
     LIVE_QUIZ
+    POLL
     PRACTICE_QUIZ
     MICRO_LEARNING
     GROUP_ACTIVITY
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/prisma/src/prisma/schema/sharing.prisma` around lines 459 - 460, The
ObjectType enum is missing a POLL value which prevents
ActivityLogEntry.objectType and AuditLogEntry.objectType from representing
poll-related entries even though ActivityLogEntry has pollId; update the
ObjectType enum to include POLL so logs can identify polls, then run any prisma
generate/migrate steps to apply the schema change and ensure
ActivityLogEntry/AuditLogEntry usages continue to compile against the updated
enum.

@sjschlapbach sjschlapbach marked this pull request as ready for review March 14, 2026 23:32
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Mar 14, 2026
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
12.3% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/types/src/index.ts (1)

49-55: ⚠️ Potential issue | 🟠 Major

Update the remaining ActivityType switches for polls.

Adding POLL here makes apps/frontend-manage/src/components/courses/modals/TemplateDeletionModal.tsx:57-75 and apps/frontend-manage/src/components/activities/overview/ActivityNameChangeModal.tsx:89-115 non-exhaustive. Both still fall through default, so poll rename and template-deletion flows will skip the local activity-list update logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/types/src/index.ts` around lines 49 - 55, Adding POLL to
ActivityType makes existing switch statements in TemplateDeletionModal and
ActivityNameChangeModal non-exhaustive so poll flows fall through to default and
skip local activity-list updates; update the switches that inspect ActivityType
(in components TemplateDeletionModal.tsx and ActivityNameChangeModal.tsx) to
explicitly handle ActivityType.POLL and apply the same local update/removal
logic used for the other activity types (mirror the branch used for
LIVE_QUIZ/PRACTICE_QUIZ or GROUP_ACTIVITY), ensuring polls update/delete from
the local activity list rather than hitting the default case.
packages/graphql/src/services/elements.ts (1)

119-129: ⚠️ Potential issue | 🟠 Major

Finish the poll rollout in the instance helpers.

This makes poll-backed elements discoverable in getUserElements, but the rest of packages/graphql/src/services/elements.ts still ignores polls in getSingleElementInstance, getInstanceUpdateActivities, updateElementInstances, flagOutdatedElementInstances, and the derived-access summary logic. An element used in a poll can therefore be found here, yet updates/outdated flags for the corresponding poll instances will still be skipped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/services/elements.ts` around lines 119 - 129, The
poll-backed element case was only added to the discovery path but not to the
instance/update/outdated paths; update the instance helpers and related logic
(specifically getSingleElementInstance, getInstanceUpdateActivities,
updateElementInstances, flagOutdatedElementInstances, and the derived-access
summary code) to treat poll-backed elements the same as other activity-backed
ones by including checks for elementStack.pollId (and any elementBlock pollId
variants if applicable) wherever the code currently checks for liveQuizId,
practiceQuizId, microLearningId, or groupActivityId; mirror the existing
conditional branches, OR filters, and update/flagging logic to include pollId so
poll instances are returned, updated, and marked outdated consistently.
packages/util/src/permissions/accessRequest.ts (1)

89-100: ⚠️ Potential issue | 🔴 Critical

Add pollId to the soft-delete filter.

When this runs for a soft-deleted poll, every other object id here is undefined, so Line 90 becomes an unscoped deleteMany() and wipes all access requests instead of just the poll’s rows.

💥 Minimal fix
     await prisma.accessRequest.deleteMany({
       where: {
         catalogCollectionId,
         answerCollectionId,
         elementId,
         courseId,
         liveQuizId,
+        pollId,
         practiceQuizId,
         microLearningId,
         groupActivityId,
       },
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/util/src/permissions/accessRequest.ts` around lines 89 - 100, The
soft-delete branch using prisma.accessRequest.deleteMany is missing pollId in
the where filter which causes an unscoped delete when other ids are undefined;
update the deleteMany call (the where object used when objectSoftDeleted is
true) to include pollId (matching the other keys like catalogCollectionId,
elementId, etc.) so the query is properly scoped to the soft-deleted poll rows.
packages/graphql/src/services/sharing.ts (1)

5645-5740: ⚠️ Potential issue | 🟠 Major

Wire polls into validateActivityPermissions() as well.

Line 5729 adds the low-level pollId access check, but validateActivityPermissions() in this file still falls through to false for ActivityType.POLL. Any caller that uses the generic activity helper will keep rejecting poll operations even when the derived permission exists.

🛠️ Suggested follow-up
+  } else if (activityType === ActivityType.POLL) {
+    const valid = await checkAccess(
+      [
+        {
+          pollId: activityId,
+          minimumPermissionLevel,
+        },
+      ],
+      ctx
+    )
+
+    return valid
   } else if (activityType === ActivityType.PRACTICE_QUIZ) {

Also applies to: 5976-6000

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/services/sharing.ts` around lines 5645 - 5740, The
checkAccess function adds a derivedPermission check for pollId but
validateActivityPermissions still falls through to false for ActivityType.POLL;
update validateActivityPermissions to handle ActivityType.POLL by querying
ctx.prisma.derivedPermission for pollId using the same pattern as other activity
types (use pollId_userId and acceptedPermissionLevels[minimumPermissionLevel])
so poll activities are validated correctly; reference
validateActivityPermissions, ActivityType.POLL, and the derivedPermission
pollId_userId lookup to locate where to add the case.
♻️ Duplicate comments (1)
packages/prisma/src/prisma/schema/sharing.prisma (1)

467-468: ⚠️ Potential issue | 🟠 Major

Add POLL to ObjectType as well.

ActivityLogEntry.objectType and AuditLogEntry.objectType still depend on the enum at Lines 394-404, which currently has no POLL member. With this relation in place, poll log entries still have no valid discriminator.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/prisma/src/prisma/schema/sharing.prisma` around lines 467 - 468, The
ObjectType enum used by ActivityLogEntry.objectType and AuditLogEntry.objectType
is missing the POLL member, so add an entry named POLL to the ObjectType enum
(the enum declared around the previous lines 394-404) so that the poll relation
on the model using poll and pollId has a valid discriminator; update the enum
definition to include POLL exactly matching the naming style of other members to
restore validity for log entries referencing polls.
🧹 Nitpick comments (7)
packages/util/src/permissions/poll.ts (1)

8-15: Use the configured path aliases in this new module.

Since this file is new, it's a good point to avoid adding more relative imports for internal modules here.

As per coding guidelines: Use @ and ~ path aliases for imports instead of relative paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/util/src/permissions/poll.ts` around lines 8 - 15, The new module
currently uses relative imports for PrismaTransactionClient,
updateAccessRequestInstances, and the util exports
(getActivityPermissionsObject, getActivityPermissionsUser,
propagateActivityToElements, propagateActivityToElementsUser); update these
import statements to use the project's configured path aliases (e.g., replace
relative paths with `@/`... or ~/... aliases) so the imports use the canonical
internal module paths and match the coding guidelines.
apps/frontend-manage/src/components/activities/creation/poll/PollDescriptionStep.tsx (1)

1-3: Use alias imports in new frontend files.

Please switch ../DescriptionStep and ./PollWizard to the repo aliases instead of introducing new relative imports here.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use @ and ~ path aliases for imports instead of relative paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/frontend-manage/src/components/activities/creation/poll/PollDescriptionStep.tsx`
around lines 1 - 3, The imports in PollDescriptionStep.tsx use relative paths;
replace the relative imports of DescriptionStep and PollWizard with the
repository path aliases (use @ or ~ as per project guidelines) so they follow
the alias import convention: update the import of DescriptionStep and the import
of PollWizard (and any related types like PollWizardStepProps) to use the
alias-based module paths instead of "../DescriptionStep" and "./PollWizard".
apps/frontend-manage/src/components/activities/ActivityCreation.tsx (1)

2-16: Import the new poll operation from the package entrypoint.

GetSinglePollDocument is being added via @klicker-uzh/graphql/dist/ops, but app/package code should consume generated operations from @klicker-uzh/graphql instead of the internal dist path.

As per coding guidelines, **/{apps,packages}/**/*.{ts,tsx}: Import GraphQL operations from @klicker-uzh/graphql package.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend-manage/src/components/activities/ActivityCreation.tsx` around
lines 2 - 16, The import for GetSinglePollDocument (and any other GraphQL
operations in the same import list like GetActiveUserCoursesDocument,
GetGroupActivityDocument, GetSingleLiveQuizDocument,
GetSingleMicroLearningDocument, GetSinglePracticeQuizDocument, etc.) must be
taken from the package entrypoint instead of the internal dist path; update the
import source in ActivityCreation.tsx to import these operation symbols from
'@klicker-uzh/graphql' (not '@klicker-uzh/graphql/dist/ops') so the code follows
the package-level consumption guideline.
apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx (2)

23-35: Tighten PollWizardStepProps before more steps depend on it.

formRef and validationSchema are any, and both navigation callbacks are optional even though the downstream steps call them unconditionally. That weakens strict mode and is already forcing !/as any in the poll step components. As per coding guidelines "Use TypeScript strict mode everywhere".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx`
around lines 23 - 35, The PollWizardStepProps interface uses overly loose types
and optional navigation callbacks which forces unsafe casts downstream; update
PollWizardStepProps so formRef is a properly typed ref (e.g., React.RefObject or
MutableRefObject for the Formik/Form values tied to PollFormValues), change
validationSchema from any to a concrete Yup schema type (e.g.,
Yup.SchemaOf<PollFormValues> or appropriate Schema-like type used across the
app), and make onPrevStep and onNextStep required (remove the optional ?) so
downstream components can call them without non-null assertions; update
imports/types as needed where PollWizardStepProps is consumed.

2-9: Use the @klicker-uzh/graphql entrypoint here.

These new poll wizard imports reach into @klicker-uzh/graphql/dist/ops instead of the package’s public surface. Please switch this file (and the helper beside it) to the package entrypoint so generated ops stay behind the supported boundary. As per coding guidelines "Import GraphQL operations from @klicker-uzh/graphql package".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx`
around lines 2 - 9, The imports in PollWizard.tsx use the internal path
'@klicker-uzh/graphql/dist/ops'; change them to import the same symbols
(CreatePollDocument, EditPollDocument, Element, ElementType, Poll,
PublicationStatus) from the package public entrypoint '@klicker-uzh/graphql'
instead, and make the identical change in the adjacent helper module that also
imports GraphQL ops so all generated operations are imported via the supported
package surface.
apps/frontend-manage/src/components/activities/creation/StackCreationStep.tsx (1)

17-17: Use a path alias for the new poll import.

This adds another relative import in a path where the repo expects @/~ aliases.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: Use @ and ~ path aliases for imports instead of relative paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/frontend-manage/src/components/activities/creation/StackCreationStep.tsx`
at line 17, Replace the relative import of PollWizardStepProps from
'./poll/PollWizard' with the repository path-alias import (use @ or ~) so it
follows the project's alias convention; locate the import statement referencing
PollWizard and update it to the equivalent alias-based path that points to the
PollWizard module, keeping the exported symbol PollWizardStepProps unchanged.
packages/prisma/src/prisma/schema/migrations/20260314220223_polls/migration.sql (1)

55-61: Add an index for PollInstance.pollId.

This table will grow with every generated poll link. Without an index on the child FK, querying instances by poll and cascading deletes from Poll will degrade to seq scans.

🧭 Suggested index
 CREATE TABLE "public"."PollInstance" (
     "id" UUID NOT NULL,
     "pollId" UUID NOT NULL,
     "lastAnsweredStackId" INTEGER,
     "completed" BOOLEAN NOT NULL DEFAULT false,

     CONSTRAINT "PollInstance_pkey" PRIMARY KEY ("id")
 );
+
+CREATE INDEX "PollInstance_pollId_idx" ON "public"."PollInstance"("pollId");

Also applies to: 100-101

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/prisma/src/prisma/schema/migrations/20260314220223_polls/migration.sql`
around lines 55 - 61, The migration is missing an index on the foreign key
column pollId in the PollInstance table which will cause slow sequential scans;
add a dedicated index (e.g. name it PollInstance_pollId_idx) on
"PollInstance"."pollId" by appending a CREATE INDEX statement for
PollInstance.pollId in this migration and add the same CREATE INDEX to the other
migrations referenced (the migrations at the other occurrence noted as 100-101)
so queries filtering by pollId and cascade/delete operations use the index.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/frontend-manage/src/components/activities/ActivityCreation.tsx`:
- Around line 261-279: The Poll wizard block currently uses LiveQuiz values;
update the PollWizard props so they reference ActivityType.Poll instead of
ActivityType.LiveQuiz: change the title key from t('shared.generic.liveQuiz') to
the appropriate poll title key, and replace both mode checks (editMode ===
ActivityType.LiveQuiz and duplicationMode === ActivityType.LiveQuiz) with checks
against ActivityType.Poll; ensure the initialValues/duplication branch that sets
the copied name still uses dataPoll?.getSinglePoll and only treats
duplicationMode as ActivityType.Poll so edit/duplication flows for Polls behave
correctly.

In
`@apps/frontend-manage/src/components/activities/creation/poll/PollInformationStep.tsx`:
- Around line 69-108: In PollInformationStep (inside the t.rich callbacks for
'manage.activityWizard.pollLecturerDocs' and
'manage.activityWizard.pollStudentDocs' and the earlier link), update each
anchor element that uses target="_blank" to also include rel="noopener
noreferrer" to prevent window.opener access from the opened pages; locate the
three anchor tags inside the array of info items (the ones rendering hrefs for
"use_cases/poll", "tutorials/poll", and "student_tutorials/poll") and add the
rel attribute to each.

In `@apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx`:
- Around line 183-208: The handleSubmit in PollWizard.tsx is calling
submitPollForm without awaiting it, allowing Formik's isSubmitting to clear
before the async work finishes; update the handleSubmit callback to await the
call to submitPollForm (i.e. make the invocation "await submitPollForm(...)"
inside handleSubmit) so Formik remains in submitting state until the helper
(submitPollForm) completes or throws, preserving edit/create behavior and
avoiding double-submits.

In
`@apps/frontend-manage/src/components/activities/creation/poll/submitPollForm.ts`:
- Around line 82-93: When editMode is true but id is missing, the current code
falls back to calling createPoll; instead, guard the edit path in the submit
logic (the block that currently calls editPoll and createPoll): if editMode &&
!id, abort and surface an error (throw or return a rejected result) so you do
not call createPoll; otherwise proceed to call editPoll({ variables: { id,
...createOrUpdateJSON } }) when id exists, and only call createPoll({ variables:
createOrUpdateJSON }) when editMode is false.

In `@packages/graphql/src/index.ts`:
- Line 18: The Poll schema includes endDate and scheduledCompletionTaskId but
there is no scheduled-task handler; add a handler (e.g., handleEndExpiredPoll or
handlePublishScheduledPoll) in packages/graphql/src/services/polls.ts that
mirrors the pattern used by
handlePublishScheduledPracticeQuiz/handlePublishScheduledMicroLearning/handlePublishScheduledLiveQuiz/handleEndExpiredGroupActivity:
locate the Poll-related logic, implement a function that verifies
scheduledCompletionTaskId, checks endDate, performs the completion/expiry
actions, and then clears or nulls scheduledCompletionTaskId; finally register
this handler in the handlers map so scheduled poll tasks are executed, or
alternatively remove endDate and scheduledCompletionTaskId from the Poll schema
if scheduling is not supported.

In `@packages/graphql/src/schema/query.ts`:
- Around line 666-677: The generic object resolvers getObjectPermissions,
getDerivedObjectPermissions, and getObjectActivity need to handle polls: add a
DB.ObjectType.POLL branch in each resolver that delegates to the PollService
equivalents (e.g., PollService.getPermissions / getDerivedPermissions /
getActivity or create these service methods if missing) and ensure they accept
the same args shape used by getSinglePoll (pollId from args.id) and respect
permission checks; update the schema resolvers in query.ts to route POLL cases
to the PollService, add appropriate error handling and null returns consistent
with other object types, and test in GraphQL Playground to confirm polls now
return permissions and activity instead of null.

In `@packages/graphql/src/services/polls.ts`:
- Around line 41-48: The server currently accepts any referenced element
id/instance from splitActivityInstances; add server-side validation to ensure
only frontend-supported element types are allowed (SC, MC, KPRIM, Numerical,
FreeText, Content, Selection, CaseStudy). In the polls mutation (the code
invoking splitActivityInstances and later processing
persistentInstanceIds/persistentInstances/elementMap), inspect each referenced
element's type via elementMap (or the element objects in persistentInstances)
and either filter out or reject the request with a clear error when an
unsupported type is detected; apply the same validation logic for the second
block around lines 81-99 where instances are processed. Ensure the validation
runs before persisting/updating poll entries so crafted requests cannot save
unsupported element types.
- Around line 165-168: The invalidate event currently emits the input `id` which
is undefined on create; update the emitter call in the create path (where
`ctx.emitter.emit('invalidate', { typename: 'Poll', id, })` is located) to use
the persisted poll identifier instead (emit `activity.id` rather than `id`),
ensuring the created Poll's id is sent as the event target; keep the event shape
(typename: 'Poll', id: activity.id) and only change the id value in that branch.

In `@packages/i18n/messages/de.ts`:
- Around line 2002-2003: The two i18n keys are inverted: pollCreationFailed
currently contains the editing-failed text and pollEditingFailed contains the
creation-failed text; update the values so pollCreationFailed holds the
creation-failed message and pollEditingFailed holds the editing-failed message
(i.e., swap or correct the string literals associated with the
pollCreationFailed and pollEditingFailed keys in the de.ts messages).

In `@packages/i18n/messages/en.ts`:
- Around line 1986-1987: The two i18n keys pollCreationFailed and
pollEditingFailed have their messages inverted; update the value for
pollCreationFailed to "Failed to create the poll..." and the value for
pollEditingFailed to "Failed to customize the poll..." so the UI displays the
correct error copy for each failure path (edit the string literals assigned to
pollCreationFailed and pollEditingFailed).

In
`@packages/prisma/src/prisma/schema/migrations/20260314220223_polls/migration.sql`:
- Around line 85-86: The migration creates a UNIQUE index on ElementStack.pollId
which enforces one stack per Poll and will cause inserts to fail when
Poll.stacks contains multiple stacks; update the migration so
ElementStack.pollId is not unique (replace the UNIQUE index with a
regular/non-unique index or drop the index entirely) so multiple ElementStack
rows can reference the same Poll, ensuring Poll.stacks and lastAnsweredStackId
work as intended; update the CREATE INDEX statement that currently names
"ElementStack_pollId_key" and re-run/adjust the migration accordingly.

---

Outside diff comments:
In `@packages/graphql/src/services/elements.ts`:
- Around line 119-129: The poll-backed element case was only added to the
discovery path but not to the instance/update/outdated paths; update the
instance helpers and related logic (specifically getSingleElementInstance,
getInstanceUpdateActivities, updateElementInstances,
flagOutdatedElementInstances, and the derived-access summary code) to treat
poll-backed elements the same as other activity-backed ones by including checks
for elementStack.pollId (and any elementBlock pollId variants if applicable)
wherever the code currently checks for liveQuizId, practiceQuizId,
microLearningId, or groupActivityId; mirror the existing conditional branches,
OR filters, and update/flagging logic to include pollId so poll instances are
returned, updated, and marked outdated consistently.

In `@packages/graphql/src/services/sharing.ts`:
- Around line 5645-5740: The checkAccess function adds a derivedPermission check
for pollId but validateActivityPermissions still falls through to false for
ActivityType.POLL; update validateActivityPermissions to handle
ActivityType.POLL by querying ctx.prisma.derivedPermission for pollId using the
same pattern as other activity types (use pollId_userId and
acceptedPermissionLevels[minimumPermissionLevel]) so poll activities are
validated correctly; reference validateActivityPermissions, ActivityType.POLL,
and the derivedPermission pollId_userId lookup to locate where to add the case.

In `@packages/types/src/index.ts`:
- Around line 49-55: Adding POLL to ActivityType makes existing switch
statements in TemplateDeletionModal and ActivityNameChangeModal non-exhaustive
so poll flows fall through to default and skip local activity-list updates;
update the switches that inspect ActivityType (in components
TemplateDeletionModal.tsx and ActivityNameChangeModal.tsx) to explicitly handle
ActivityType.POLL and apply the same local update/removal logic used for the
other activity types (mirror the branch used for LIVE_QUIZ/PRACTICE_QUIZ or
GROUP_ACTIVITY), ensuring polls update/delete from the local activity list
rather than hitting the default case.

In `@packages/util/src/permissions/accessRequest.ts`:
- Around line 89-100: The soft-delete branch using
prisma.accessRequest.deleteMany is missing pollId in the where filter which
causes an unscoped delete when other ids are undefined; update the deleteMany
call (the where object used when objectSoftDeleted is true) to include pollId
(matching the other keys like catalogCollectionId, elementId, etc.) so the query
is properly scoped to the soft-deleted poll rows.

---

Duplicate comments:
In `@packages/prisma/src/prisma/schema/sharing.prisma`:
- Around line 467-468: The ObjectType enum used by ActivityLogEntry.objectType
and AuditLogEntry.objectType is missing the POLL member, so add an entry named
POLL to the ObjectType enum (the enum declared around the previous lines
394-404) so that the poll relation on the model using poll and pollId has a
valid discriminator; update the enum definition to include POLL exactly matching
the naming style of other members to restore validity for log entries
referencing polls.

---

Nitpick comments:
In `@apps/frontend-manage/src/components/activities/ActivityCreation.tsx`:
- Around line 2-16: The import for GetSinglePollDocument (and any other GraphQL
operations in the same import list like GetActiveUserCoursesDocument,
GetGroupActivityDocument, GetSingleLiveQuizDocument,
GetSingleMicroLearningDocument, GetSinglePracticeQuizDocument, etc.) must be
taken from the package entrypoint instead of the internal dist path; update the
import source in ActivityCreation.tsx to import these operation symbols from
'@klicker-uzh/graphql' (not '@klicker-uzh/graphql/dist/ops') so the code follows
the package-level consumption guideline.

In
`@apps/frontend-manage/src/components/activities/creation/poll/PollDescriptionStep.tsx`:
- Around line 1-3: The imports in PollDescriptionStep.tsx use relative paths;
replace the relative imports of DescriptionStep and PollWizard with the
repository path aliases (use @ or ~ as per project guidelines) so they follow
the alias import convention: update the import of DescriptionStep and the import
of PollWizard (and any related types like PollWizardStepProps) to use the
alias-based module paths instead of "../DescriptionStep" and "./PollWizard".

In `@apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx`:
- Around line 23-35: The PollWizardStepProps interface uses overly loose types
and optional navigation callbacks which forces unsafe casts downstream; update
PollWizardStepProps so formRef is a properly typed ref (e.g., React.RefObject or
MutableRefObject for the Formik/Form values tied to PollFormValues), change
validationSchema from any to a concrete Yup schema type (e.g.,
Yup.SchemaOf<PollFormValues> or appropriate Schema-like type used across the
app), and make onPrevStep and onNextStep required (remove the optional ?) so
downstream components can call them without non-null assertions; update
imports/types as needed where PollWizardStepProps is consumed.
- Around line 2-9: The imports in PollWizard.tsx use the internal path
'@klicker-uzh/graphql/dist/ops'; change them to import the same symbols
(CreatePollDocument, EditPollDocument, Element, ElementType, Poll,
PublicationStatus) from the package public entrypoint '@klicker-uzh/graphql'
instead, and make the identical change in the adjacent helper module that also
imports GraphQL ops so all generated operations are imported via the supported
package surface.

In
`@apps/frontend-manage/src/components/activities/creation/StackCreationStep.tsx`:
- Line 17: Replace the relative import of PollWizardStepProps from
'./poll/PollWizard' with the repository path-alias import (use @ or ~) so it
follows the project's alias convention; locate the import statement referencing
PollWizard and update it to the equivalent alias-based path that points to the
PollWizard module, keeping the exported symbol PollWizardStepProps unchanged.

In
`@packages/prisma/src/prisma/schema/migrations/20260314220223_polls/migration.sql`:
- Around line 55-61: The migration is missing an index on the foreign key column
pollId in the PollInstance table which will cause slow sequential scans; add a
dedicated index (e.g. name it PollInstance_pollId_idx) on
"PollInstance"."pollId" by appending a CREATE INDEX statement for
PollInstance.pollId in this migration and add the same CREATE INDEX to the other
migrations referenced (the migrations at the other occurrence noted as 100-101)
so queries filtering by pollId and cascade/delete operations use the index.

In `@packages/util/src/permissions/poll.ts`:
- Around line 8-15: The new module currently uses relative imports for
PrismaTransactionClient, updateAccessRequestInstances, and the util exports
(getActivityPermissionsObject, getActivityPermissionsUser,
propagateActivityToElements, propagateActivityToElementsUser); update these
import statements to use the project's configured path aliases (e.g., replace
relative paths with `@/`... or ~/... aliases) so the imports use the canonical
internal module paths and match the coding guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 010b54c7-e8ec-4b14-96e9-f6629c3a4527

📥 Commits

Reviewing files that changed from the base of the PR and between 99191f0 and 25ad402.

⛔ Files ignored due to path filters (10)
  • packages/graphql/src/graphql/ops/FPollData.graphql is excluded by !**/**/graphql/ops/**
  • packages/graphql/src/graphql/ops/MCreatePoll.graphql is excluded by !**/**/graphql/ops/**
  • packages/graphql/src/graphql/ops/MEditPoll.graphql is excluded by !**/**/graphql/ops/**
  • packages/graphql/src/graphql/ops/QGetCourseActivityIds.graphql is excluded by !**/**/graphql/ops/**
  • packages/graphql/src/graphql/ops/QGetSinglePoll.graphql is excluded by !**/**/graphql/ops/**
  • packages/graphql/src/ops.schema.json is excluded by !**/**/ops.schema.json
  • packages/graphql/src/ops.ts is excluded by !**/**/ops.ts
  • packages/graphql/src/public/client.json is excluded by !**/**/public/**
  • packages/graphql/src/public/schema.graphql is excluded by !**/**/public/**
  • packages/graphql/src/public/server.json is excluded by !**/**/public/**
📒 Files selected for processing (31)
  • apps/frontend-manage/src/components/activities/ActivityCreation.tsx
  • apps/frontend-manage/src/components/activities/creation/DescriptionStep.tsx
  • apps/frontend-manage/src/components/activities/creation/InstanceUpdateOption.tsx
  • apps/frontend-manage/src/components/activities/creation/StackCreationStep.tsx
  • apps/frontend-manage/src/components/activities/creation/SuspendedCreationButtons.tsx
  • apps/frontend-manage/src/components/activities/creation/WizardLayout.tsx
  • apps/frontend-manage/src/components/activities/creation/poll/PollDescriptionStep.tsx
  • apps/frontend-manage/src/components/activities/creation/poll/PollInformationStep.tsx
  • apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx
  • apps/frontend-manage/src/components/activities/creation/poll/submitPollForm.ts
  • apps/frontend-manage/src/components/activities/overview/ActivityOverviewFilters.tsx
  • apps/frontend-manage/src/components/elements/tags/SuspendedActivitySelection.tsx
  • packages/graphql/src/index.ts
  • packages/graphql/src/schema/activities.ts
  • packages/graphql/src/schema/mutation.ts
  • packages/graphql/src/schema/poll.ts
  • packages/graphql/src/schema/query.ts
  • packages/graphql/src/services/activities.ts
  • packages/graphql/src/services/elements.ts
  • packages/graphql/src/services/polls.ts
  • packages/graphql/src/services/sharing.ts
  • packages/i18n/messages/de.ts
  • packages/i18n/messages/en.ts
  • packages/prisma/src/prisma/schema/migrations/20260314220223_polls/migration.sql
  • packages/prisma/src/prisma/schema/migrations/20260314234850_activity_view_polls/migration.sql
  • packages/prisma/src/prisma/schema/quiz.prisma
  • packages/prisma/src/prisma/schema/sharing.prisma
  • packages/types/src/index.ts
  • packages/util/src/permissions.ts
  • packages/util/src/permissions/accessRequest.ts
  • packages/util/src/permissions/poll.ts

Comment on lines +261 to +279
{creationMode === ActivityType.Poll && (
<PollWizard
title={t('shared.generic.liveQuiz')}
closeWizard={closeWizard}
initialValues={
dataPoll?.getSinglePoll
? duplicationMode === ActivityType.LiveQuiz
? {
...dataPoll.getSinglePoll,
name: `${dataPoll.getSinglePoll.name} (Copy)`,
}
: dataPoll.getSinglePoll
: undefined
}
selection={selectedElements}
resetSelection={resetSelection}
editMode={editMode === ActivityType.LiveQuiz}
duplicationMode={duplicationMode === ActivityType.LiveQuiz}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use ActivityType.Poll throughout the Poll wizard block.

This is still wired like a Live Quiz: the title key and all three mode checks use ActivityType.LiveQuiz. As a result, poll edit/duplication flows will be treated as fresh creation and the header text is wrong.

Suggested fix
-            title={t('shared.generic.liveQuiz')}
+            title={t('shared.generic.poll')}
             closeWizard={closeWizard}
             initialValues={
               dataPoll?.getSinglePoll
-                ? duplicationMode === ActivityType.LiveQuiz
+                ? duplicationMode === ActivityType.Poll
                   ? {
                       ...dataPoll.getSinglePoll,
                       name: `${dataPoll.getSinglePoll.name} (Copy)`,
                     }
                   : dataPoll.getSinglePoll
                 : undefined
             }
             selection={selectedElements}
             resetSelection={resetSelection}
-            editMode={editMode === ActivityType.LiveQuiz}
-            duplicationMode={duplicationMode === ActivityType.LiveQuiz}
+            editMode={editMode === ActivityType.Poll}
+            duplicationMode={duplicationMode === ActivityType.Poll}
           />
📝 Committable suggestion

‼️ 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.

Suggested change
{creationMode === ActivityType.Poll && (
<PollWizard
title={t('shared.generic.liveQuiz')}
closeWizard={closeWizard}
initialValues={
dataPoll?.getSinglePoll
? duplicationMode === ActivityType.LiveQuiz
? {
...dataPoll.getSinglePoll,
name: `${dataPoll.getSinglePoll.name} (Copy)`,
}
: dataPoll.getSinglePoll
: undefined
}
selection={selectedElements}
resetSelection={resetSelection}
editMode={editMode === ActivityType.LiveQuiz}
duplicationMode={duplicationMode === ActivityType.LiveQuiz}
/>
{creationMode === ActivityType.Poll && (
<PollWizard
title={t('shared.generic.poll')}
closeWizard={closeWizard}
initialValues={
dataPoll?.getSinglePoll
? duplicationMode === ActivityType.Poll
? {
...dataPoll.getSinglePoll,
name: `${dataPoll.getSinglePoll.name} (Copy)`,
}
: dataPoll.getSinglePoll
: undefined
}
selection={selectedElements}
resetSelection={resetSelection}
editMode={editMode === ActivityType.Poll}
duplicationMode={duplicationMode === ActivityType.Poll}
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend-manage/src/components/activities/ActivityCreation.tsx` around
lines 261 - 279, The Poll wizard block currently uses LiveQuiz values; update
the PollWizard props so they reference ActivityType.Poll instead of
ActivityType.LiveQuiz: change the title key from t('shared.generic.liveQuiz') to
the appropriate poll title key, and replace both mode checks (editMode ===
ActivityType.LiveQuiz and duplicationMode === ActivityType.LiveQuiz) with checks
against ActivityType.Poll; ensure the initialValues/duplication branch that sets
the copied name still uses dataPoll?.getSinglePoll and only treats
duplicationMode as ActivityType.Poll so edit/duplication flows for Polls behave
correctly.

Comment on lines +69 to +108
link: (text) => (
<a
href="https://www.klicker.uzh.ch/use_cases/poll/"
target="_blank"
className="underline"
>
{text}
</a>
),
}),
},
{
icon: faBookOpen,
iconColor: 'text-uzh-blue-100',
richText: t.rich(
'manage.activityWizard.pollLecturerDocs',
{
link: (text) => (
<a
href="https://www.klicker.uzh.ch/tutorials/poll/"
target="_blank"
className="underline"
>
{text}
</a>
),
}
),
},
{
icon: faUsers,
iconColor: 'text-black',
richText: t.rich(
'manage.activityWizard.pollStudentDocs',
{
link: (text) => (
<a
href="https://www.klicker.uzh.ch/student_tutorials/poll/"
target="_blank"
className="underline"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd "PollInformationStep.tsx" --type f

Repository: uzh-bf/klicker-uzh

Length of output: 146


🏁 Script executed:

cat -n "apps/frontend-manage/src/components/activities/creation/poll/PollInformationStep.tsx" | sed -n '65,110p'

Repository: uzh-bf/klicker-uzh

Length of output: 2258


🏁 Script executed:

rg 'rel=' "apps/frontend-manage/src/components/activities/creation/poll/PollInformationStep.tsx" | head -20

Repository: uzh-bf/klicker-uzh

Length of output: 44


🏁 Script executed:

rg 'target="_blank"' "apps/frontend-manage/src/components/activities/creation/poll/PollInformationStep.tsx" -n

Repository: uzh-bf/klicker-uzh

Length of output: 207


Add rel="noopener noreferrer" to all external links opened in a new tab.

Three anchor elements at lines 72, 89, and 107 open external URLs with target="_blank" without the rel attribute, allowing the opened page to access window.opener and potentially compromise security.

Fix
                          <a
                            href="https://www.klicker.uzh.ch/use_cases/poll/"
                            target="_blank"
+                           rel="noopener noreferrer"
                            className="underline"
                          >

Apply the same change to the links at lines 89 and 107.

📝 Committable suggestion

‼️ 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.

Suggested change
link: (text) => (
<a
href="https://www.klicker.uzh.ch/use_cases/poll/"
target="_blank"
className="underline"
>
{text}
</a>
),
}),
},
{
icon: faBookOpen,
iconColor: 'text-uzh-blue-100',
richText: t.rich(
'manage.activityWizard.pollLecturerDocs',
{
link: (text) => (
<a
href="https://www.klicker.uzh.ch/tutorials/poll/"
target="_blank"
className="underline"
>
{text}
</a>
),
}
),
},
{
icon: faUsers,
iconColor: 'text-black',
richText: t.rich(
'manage.activityWizard.pollStudentDocs',
{
link: (text) => (
<a
href="https://www.klicker.uzh.ch/student_tutorials/poll/"
target="_blank"
className="underline"
link: (text) => (
<a
href="https://www.klicker.uzh.ch/use_cases/poll/"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
{text}
</a>
),
}),
},
{
icon: faBookOpen,
iconColor: 'text-uzh-blue-100',
richText: t.rich(
'manage.activityWizard.pollLecturerDocs',
{
link: (text) => (
<a
href="https://www.klicker.uzh.ch/tutorials/poll/"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
{text}
</a>
),
}
),
},
{
icon: faUsers,
iconColor: 'text-black',
richText: t.rich(
'manage.activityWizard.pollStudentDocs',
{
link: (text) => (
<a
href="https://www.klicker.uzh.ch/student_tutorials/poll/"
target="_blank"
rel="noopener noreferrer"
className="underline"
🧰 Tools
🪛 Biome (2.4.6)

[error] 72-72: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

(lint/security/noBlankTarget)


[error] 89-89: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

(lint/security/noBlankTarget)


[error] 107-107: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

(lint/security/noBlankTarget)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/frontend-manage/src/components/activities/creation/poll/PollInformationStep.tsx`
around lines 69 - 108, In PollInformationStep (inside the t.rich callbacks for
'manage.activityWizard.pollLecturerDocs' and
'manage.activityWizard.pollStudentDocs' and the earlier link), update each
anchor element that uses target="_blank" to also include rel="noopener
noreferrer" to prevent window.opener access from the opened pages; locate the
three anchor tags inside the array of info items (the ones rendering hrefs for
"use_cases/poll", "tutorials/poll", and "student_tutorials/poll") and add the
rel attribute to each.

Comment on lines +183 to +208
const handleSubmit = useCallback(
async (values: PollFormValues) => {
submitPollForm({
id: initialValues?.id,
values,
editMode,
createPoll,
editPoll,
setIsWizardCompleted,
onError: () =>
toast({
type: 'error',
message: (
<div>
<div>
{editMode
? t('manage.activityWizard.pollEditingFailed')
: t('manage.activityWizard.pollCreationFailed')}
</div>
<div>{t('manage.activityWizard.considerFormErrors')}</div>
</div>
),
options: { duration: 6000 },
}),
})
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "PollWizard.tsx" | head -20

Repository: uzh-bf/klicker-uzh

Length of output: 139


🏁 Script executed:

rg "submitPollForm" --type ts --type tsx -l | head -10

Repository: uzh-bf/klicker-uzh

Length of output: 89


🏁 Script executed:

rg "const submitPollForm" --type ts --type tsx -A 30 | head -100

Repository: uzh-bf/klicker-uzh

Length of output: 89


🏁 Script executed:

rg "const submitPollForm" -A 30 | head -150

Repository: uzh-bf/klicker-uzh

Length of output: 44


🏁 Script executed:

cat -n apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx | head -250

Repository: uzh-bf/klicker-uzh

Length of output: 9423


🏁 Script executed:

find . -type f -name "submitPollForm*"

Repository: uzh-bf/klicker-uzh

Length of output: 142


🏁 Script executed:

cat -n apps/frontend-manage/src/components/activities/creation/poll/submitPollForm.ts | head -150

Repository: uzh-bf/klicker-uzh

Length of output: 3382


🏁 Script executed:

cat -n apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx | sed -n '210,350p'

Repository: uzh-bf/klicker-uzh

Length of output: 4608


🏁 Script executed:

rg "formRef" apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx -A 5 -B 5

Repository: uzh-bf/klicker-uzh

Length of output: 2078


🏁 Script executed:

cat -n apps/frontend-manage/src/components/activities/creation/WizardLayout.tsx | head -150

Repository: uzh-bf/klicker-uzh

Length of output: 4392


🏁 Script executed:

rg "handleSubmit|onSubmit" apps/frontend-manage/src/components/activities/creation/WizardLayout.tsx -B 2 -A 2

Repository: uzh-bf/klicker-uzh

Length of output: 44


🏁 Script executed:

rg "Formik|formik" apps/frontend-manage/src/components/activities/creation/ -l | head -10

Repository: uzh-bf/klicker-uzh

Length of output: 974


🏁 Script executed:

rg "submitForm|isSubmitting" apps/frontend-manage/src/components/activities/creation/ -B 3 -A 3 | head -100

Repository: uzh-bf/klicker-uzh

Length of output: 11323


🏁 Script executed:

cat -n apps/frontend-manage/src/components/activities/creation/StackCreationStep.tsx | head -200

Repository: uzh-bf/klicker-uzh

Length of output: 6993


🏁 Script executed:

rg "onSubmit|submitForm" apps/frontend-manage/src/components/activities/creation/StackCreationStep.tsx -B 3 -A 3 | head -80

Repository: uzh-bf/klicker-uzh

Length of output: 513


🏁 Script executed:

cat -n apps/frontend-manage/src/components/activities/creation/WizardNavigation.tsx | head -120

Repository: uzh-bf/klicker-uzh

Length of output: 2894


Await submitPollForm so Formik stays submitting.

Line 185 fire-and-forgets the async submit helper, so the returned promise resolves immediately and isSubmitting can drop back to false while the mutation is still running. That makes double-submits possible on the last step.

⏳ Minimal fix
   const handleSubmit = useCallback(
     async (values: PollFormValues) => {
-      submitPollForm({
+      await submitPollForm({
         id: initialValues?.id,
         values,
         editMode,
         createPoll,
📝 Committable suggestion

‼️ 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.

Suggested change
const handleSubmit = useCallback(
async (values: PollFormValues) => {
submitPollForm({
id: initialValues?.id,
values,
editMode,
createPoll,
editPoll,
setIsWizardCompleted,
onError: () =>
toast({
type: 'error',
message: (
<div>
<div>
{editMode
? t('manage.activityWizard.pollEditingFailed')
: t('manage.activityWizard.pollCreationFailed')}
</div>
<div>{t('manage.activityWizard.considerFormErrors')}</div>
</div>
),
options: { duration: 6000 },
}),
})
},
const handleSubmit = useCallback(
async (values: PollFormValues) => {
await submitPollForm({
id: initialValues?.id,
values,
editMode,
createPoll,
editPoll,
setIsWizardCompleted,
onError: () =>
toast({
type: 'error',
message: (
<div>
<div>
{editMode
? t('manage.activityWizard.pollEditingFailed')
: t('manage.activityWizard.pollCreationFailed')}
</div>
<div>{t('manage.activityWizard.considerFormErrors')}</div>
</div>
),
options: { duration: 6000 },
}),
})
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx`
around lines 183 - 208, The handleSubmit in PollWizard.tsx is calling
submitPollForm without awaiting it, allowing Formik's isSubmitting to clear
before the async work finishes; update the handleSubmit callback to await the
call to submitPollForm (i.e. make the invocation "await submitPollForm(...)"
inside handleSubmit) so Formik remains in submitting state until the helper
(submitPollForm) completes or throws, preserving edit/create behavior and
avoiding double-submits.

Comment on lines +82 to +93
if (editMode && id) {
const result = await editPoll({
variables: { id, ...createOrUpdateJSON },
})

success = Boolean(result.data?.editPoll)
} else {
const result = await createPoll({
variables: createOrUpdateJSON,
})

success = Boolean(result.data?.createPoll)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not fall back to createPoll in edit mode.

If editMode is true but id is missing, this silently creates a new poll instead of rejecting the edit. That turns any upstream wiring bug into duplicated data.

🛑 Guard the edit path
-    if (editMode && id) {
+    if (editMode) {
+      if (!id) {
+        onError()
+        return
+      }
+
       const result = await editPoll({
         variables: { id, ...createOrUpdateJSON },
       })
 
       success = Boolean(result.data?.editPoll)
     } else {
📝 Committable suggestion

‼️ 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.

Suggested change
if (editMode && id) {
const result = await editPoll({
variables: { id, ...createOrUpdateJSON },
})
success = Boolean(result.data?.editPoll)
} else {
const result = await createPoll({
variables: createOrUpdateJSON,
})
success = Boolean(result.data?.createPoll)
if (editMode) {
if (!id) {
onError()
return
}
const result = await editPoll({
variables: { id, ...createOrUpdateJSON },
})
success = Boolean(result.data?.editPoll)
} else {
const result = await createPoll({
variables: createOrUpdateJSON,
})
success = Boolean(result.data?.createPoll)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/frontend-manage/src/components/activities/creation/poll/submitPollForm.ts`
around lines 82 - 93, When editMode is true but id is missing, the current code
falls back to calling createPoll; instead, guard the edit path in the submit
logic (the block that currently calls editPoll and createPoll): if editMode &&
!id, abort and surface an error (throw or return a rejected result) so you do
not call createPoll; otherwise proceed to call editPoll({ variables: { id,
...createOrUpdateJSON } }) when id exists, and only call createPoll({ variables:
createOrUpdateJSON }) when editMode is false.

import './schema/liveQuiz.js'
import './schema/microLearning.js'
import './schema/participant.js'
import './schema/poll.js'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

if [[ ! -f packages/graphql/src/services/polls.ts ]]; then
  echo "packages/graphql/src/services/polls.ts not found"
  exit 1
fi

echo "Poll scheduling fields / handler exports:"
rg -n 'scheduledCompletionTaskId|endDate|handle[A-Za-z]*Poll' \
  packages/prisma/src/prisma/schema/quiz.prisma \
  packages/graphql/src/services/polls.ts

echo
echo "Registered Hatchet handlers in packages/graphql/src/index.ts:"
rg -n 'handle[A-Za-z]+' packages/graphql/src/index.ts

Repository: uzh-bf/klicker-uzh

Length of output: 2150


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if file exists and its size
if [[ ! -f packages/graphql/src/services/polls.ts ]]; then
  echo "File not found"
  exit 1
fi

wc -l packages/graphql/src/services/polls.ts

echo "=== Full exports from polls.ts ==="
rg -n '^export' packages/graphql/src/services/polls.ts

echo
echo "=== Full file content ==="
cat -n packages/graphql/src/services/polls.ts

Repository: uzh-bf/klicker-uzh

Length of output: 10139


A handler for scheduled poll completion/expiry is missing.

Poll includes endDate and scheduledCompletionTaskId in the schema, indicating support for scheduling. However, unlike PracticeQuiz (which has handlePublishScheduledPracticeQuiz), MicroLearning (handlePublishScheduledMicroLearning), LiveQuiz (handlePublishScheduledLiveQuiz), and GroupActivity (which have handleEndExpiredGroupActivity and handlePublishScheduledGroupActivity), packages/graphql/src/services/polls.ts exports no handler function, and none is registered in the handlers map.

Either implement and register the missing poll scheduling handler or remove the scheduling fields from the schema.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/index.ts` at line 18, The Poll schema includes endDate
and scheduledCompletionTaskId but there is no scheduled-task handler; add a
handler (e.g., handleEndExpiredPoll or handlePublishScheduledPoll) in
packages/graphql/src/services/polls.ts that mirrors the pattern used by
handlePublishScheduledPracticeQuiz/handlePublishScheduledMicroLearning/handlePublishScheduledLiveQuiz/handleEndExpiredGroupActivity:
locate the Poll-related logic, implement a function that verifies
scheduledCompletionTaskId, checks endDate, performs the completion/expiry
actions, and then clears or nulls scheduledCompletionTaskId; finally register
this handler in the handlers map so scheduled poll tasks are executed, or
alternatively remove endDate and scheduledCompletionTaskId from the Poll schema
if scheduling is not supported.

Comment on lines +41 to +48
const {
persistentInstanceIds,
persistentInstances,
persistentInstanceOrderMap,
duplicationInstances,
elementMap,
anyInstanceOutdated,
} = await splitActivityInstances({ stacksOrBlocks: stacks }, ctx)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate poll-supported element types on the server.

The frontend only offers SC/MC/KPRIM/Numerical/FreeText/Content/Selection/CaseStudy in apps/frontend-manage/src/components/activities/creation/poll/PollWizard.tsx:23-47, but this mutation accepts any referenced element id/instance. A crafted request can still persist flashcards or other unsupported types into a poll.

Also applies to: 81-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/services/polls.ts` around lines 41 - 48, The server
currently accepts any referenced element id/instance from
splitActivityInstances; add server-side validation to ensure only
frontend-supported element types are allowed (SC, MC, KPRIM, Numerical,
FreeText, Content, Selection, CaseStudy). In the polls mutation (the code
invoking splitActivityInstances and later processing
persistentInstanceIds/persistentInstances/elementMap), inspect each referenced
element's type via elementMap (or the element objects in persistentInstances)
and either filter out or reject the request with a clear error when an
unsupported type is detected; apply the same validation logic for the second
block around lines 81-99 where instances are processed. Ensure the validation
runs before persisting/updating poll entries so crafted requests cannot save
unsupported element types.

Comment on lines +165 to +168
ctx.emitter.emit('invalidate', {
typename: 'Poll',
id,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Emit the persisted poll id on create.

On create, the input id is undefined, so the invalidate event has no target identifier. Emit activity.id here instead.

Suggested fix
   ctx.emitter.emit('invalidate', {
     typename: 'Poll',
-    id,
+    id: activity.id,
   })
📝 Committable suggestion

‼️ 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.

Suggested change
ctx.emitter.emit('invalidate', {
typename: 'Poll',
id,
})
ctx.emitter.emit('invalidate', {
typename: 'Poll',
id: activity.id,
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/services/polls.ts` around lines 165 - 168, The
invalidate event currently emits the input `id` which is undefined on create;
update the emitter call in the create path (where
`ctx.emitter.emit('invalidate', { typename: 'Poll', id, })` is located) to use
the persisted poll identifier instead (emit `activity.id` rather than `id`),
ensuring the created Poll's id is sent as the event target; keep the event shape
(typename: 'Poll', id: activity.id) and only change the id value in that branch.

Comment on lines +2002 to +2003
pollCreationFailed: 'Anpassen der Umfrage fehlgeschlagen...',
pollEditingFailed: 'Erstellen der Umfrage fehlgeschlagen...',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align the failure keys with their message text.

pollCreationFailed currently says editing failed, while pollEditingFailed says creation failed. The contract is inverted here.

Suggested fix
-      pollCreationFailed: 'Anpassen der Umfrage fehlgeschlagen...',
-      pollEditingFailed: 'Erstellen der Umfrage fehlgeschlagen...',
+      pollCreationFailed: 'Erstellen der Umfrage fehlgeschlagen...',
+      pollEditingFailed: 'Anpassen der Umfrage fehlgeschlagen...',
📝 Committable suggestion

‼️ 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.

Suggested change
pollCreationFailed: 'Anpassen der Umfrage fehlgeschlagen...',
pollEditingFailed: 'Erstellen der Umfrage fehlgeschlagen...',
pollCreationFailed: 'Erstellen der Umfrage fehlgeschlagen...',
pollEditingFailed: 'Anpassen der Umfrage fehlgeschlagen...',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/i18n/messages/de.ts` around lines 2002 - 2003, The two i18n keys are
inverted: pollCreationFailed currently contains the editing-failed text and
pollEditingFailed contains the creation-failed text; update the values so
pollCreationFailed holds the creation-failed message and pollEditingFailed holds
the editing-failed message (i.e., swap or correct the string literals associated
with the pollCreationFailed and pollEditingFailed keys in the de.ts messages).

Comment on lines +1986 to +1987
pollCreationFailed: 'Failed to customize the poll...',
pollEditingFailed: 'Failed to create the poll...',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Swap the creation/edit failure messages.

These two keys are inverted right now, so the UI will show the wrong error copy for each failure path.

💬 Suggested fix
-      pollCreationFailed: 'Failed to customize the poll...',
-      pollEditingFailed: 'Failed to create the poll...',
+      pollCreationFailed: 'Creating the poll failed...',
+      pollEditingFailed: 'Editing the poll failed...',
📝 Committable suggestion

‼️ 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.

Suggested change
pollCreationFailed: 'Failed to customize the poll...',
pollEditingFailed: 'Failed to create the poll...',
pollCreationFailed: 'Creating the poll failed...',
pollEditingFailed: 'Editing the poll failed...',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/i18n/messages/en.ts` around lines 1986 - 1987, The two i18n keys
pollCreationFailed and pollEditingFailed have their messages inverted; update
the value for pollCreationFailed to "Failed to create the poll..." and the value
for pollEditingFailed to "Failed to customize the poll..." so the UI displays
the correct error copy for each failure path (edit the string literals assigned
to pollCreationFailed and pollEditingFailed).

Comment on lines +85 to +86
-- CreateIndex
CREATE UNIQUE INDEX "ElementStack_pollId_key" ON "public"."ElementStack"("pollId");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

ElementStack.pollId should not be unique here.

Line 86 turns each poll into a one-stack activity at the database level, but the same PR models Poll.stacks as a collection and stores lastAnsweredStackId for resume state. The second stack insert will fail once a poll contains more than one stack.

🧱 Suggested fix
-CREATE UNIQUE INDEX "ElementStack_pollId_key" ON "public"."ElementStack"("pollId");
+CREATE INDEX "ElementStack_pollId_idx" ON "public"."ElementStack"("pollId");
📝 Committable suggestion

‼️ 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.

Suggested change
-- CreateIndex
CREATE UNIQUE INDEX "ElementStack_pollId_key" ON "public"."ElementStack"("pollId");
-- CreateIndex
CREATE INDEX "ElementStack_pollId_idx" ON "public"."ElementStack"("pollId");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/prisma/src/prisma/schema/migrations/20260314220223_polls/migration.sql`
around lines 85 - 86, The migration creates a UNIQUE index on
ElementStack.pollId which enforces one stack per Poll and will cause inserts to
fail when Poll.stacks contains multiple stacks; update the migration so
ElementStack.pollId is not unique (replace the UNIQUE index with a
regular/non-unique index or drop the index entirely) so multiple ElementStack
rows can reference the same Poll, ensuring Poll.stacks and lastAnsweredStackId
work as intended; update the CREATE INDEX statement that currently names
"ElementStack_pollId_key" and re-run/adjust the migration accordingly.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
packages/graphql/src/services/polls.ts (2)

40-48: ⚠️ Potential issue | 🟠 Major

Add server-side poll element-type validation before persistence.

splitActivityInstances output is used directly for connect/create, but there is no whitelist check for poll-supported element types. Crafted requests can still persist unsupported types.

Also applies to: 81-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/services/polls.ts` around lines 40 - 48, The results
returned from splitActivityInstances (used in the poll persistence paths) lack
server-side validation of element types, allowing unsupported element kinds to
be persisted; add a whitelist check after splitActivityInstances returns (both
where persistentInstances/persistentInstanceIds are used and the other
occurrence around the 81-99 block) that verifies each element's type against
allowed poll element types, reject the request (throw or return an error) if any
element has a disallowed type, and only pass validated
persistentInstances/persistentInstanceIds into the connect/create persistence
logic to ensure only supported poll element types are persisted.

165-168: ⚠️ Potential issue | 🟡 Minor

Emit the persisted poll id in invalidate events.

On create, id is undefined, so invalidation misses the created poll target. Use activity.id.

Proposed fix
   ctx.emitter.emit('invalidate', {
     typename: 'Poll',
-    id,
+    id: activity.id,
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/services/polls.ts` around lines 165 - 168, The
invalidate event currently emits the local variable id which is undefined on
create; update the emitter call in the poll create/update flow to emit the
persisted poll id (use activity.id) instead of id (or use id ?? activity.id) so
the created poll is correctly targeted; locate the emit call that uses
ctx.emitter.emit('invalidate', { typename: 'Poll', id, }) and replace the id
with activity.id (or a fallback expression) to ensure the persisted id is sent.
🧹 Nitpick comments (3)
packages/graphql/src/services/polls.ts (2)

152-155: Deduplicate element ids before recomputing derived permissions.

unlinkedElementIds may contain duplicates; recomputing repeatedly increases transaction time without benefit.

Proposed fix
-      if (unlinkedElementIds.length > 0) {
-        for (const elementId of unlinkedElementIds) {
+      if (unlinkedElementIds.length > 0) {
+        for (const elementId of new Set(unlinkedElementIds)) {
           await recomputeDerivedPermissions({ elementId }, prisma)
         }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/services/polls.ts` around lines 152 - 155,
unlinkedElementIds may contain duplicates causing redundant calls to
recomputeDerivedPermissions and longer transactions; before the loop in the
polls service, deduplicate unlinkedElementIds (e.g., convert to a Set or use
Array.from(new Set(...))) and iterate the unique list when calling await
recomputeDerivedPermissions({ elementId }, prisma) so each elementId is
processed only once.

9-11: Switch new local imports to configured aliases.

This new file introduces relative imports; please align with the repository alias rule.

As per coding guidelines: **/*.{ts,tsx,js,jsx}: Use @ and ~ path aliases for imports instead of relative paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/services/polls.ts` around lines 9 - 11, The file
currently uses relative imports for Context/ContextWithUser and the helper
functions; update those imports to use the project path aliases (e.g., use @ or
~) instead of relative paths so they follow the repository rule. Replace the
imports that bring in Context and ContextWithUser, getPermissionBooleans, and
splitActivityInstances with their aliased module paths (keep the same named
symbols) so the top of the file imports Context, ContextWithUser,
getPermissionBooleans, and splitActivityInstances via the configured alias
scheme.
packages/graphql/src/schema/mutation.ts (1)

16-16: Use project path aliases for the new service import.

Please replace the newly added relative import with the repository alias style used by the guidelines.

As per coding guidelines: **/*.{ts,tsx,js,jsx}: Use @ and ~ path aliases for imports instead of relative paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/graphql/src/schema/mutation.ts` at line 16, Replace the relative
import of the PollService with the repository path-alias form used by the
project: change the import that brings in PollService (the line importing
'../services/polls.js') to use the project alias (e.g. '@/services/polls' or
'~/services/polls' per repo config) and drop the file extension so the
PollService reference continues to work everywhere it’s used in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/graphql/src/services/polls.ts`:
- Around line 40-48: The results returned from splitActivityInstances (used in
the poll persistence paths) lack server-side validation of element types,
allowing unsupported element kinds to be persisted; add a whitelist check after
splitActivityInstances returns (both where
persistentInstances/persistentInstanceIds are used and the other occurrence
around the 81-99 block) that verifies each element's type against allowed poll
element types, reject the request (throw or return an error) if any element has
a disallowed type, and only pass validated
persistentInstances/persistentInstanceIds into the connect/create persistence
logic to ensure only supported poll element types are persisted.
- Around line 165-168: The invalidate event currently emits the local variable
id which is undefined on create; update the emitter call in the poll
create/update flow to emit the persisted poll id (use activity.id) instead of id
(or use id ?? activity.id) so the created poll is correctly targeted; locate the
emit call that uses ctx.emitter.emit('invalidate', { typename: 'Poll', id, })
and replace the id with activity.id (or a fallback expression) to ensure the
persisted id is sent.

---

Nitpick comments:
In `@packages/graphql/src/schema/mutation.ts`:
- Line 16: Replace the relative import of the PollService with the repository
path-alias form used by the project: change the import that brings in
PollService (the line importing '../services/polls.js') to use the project alias
(e.g. '@/services/polls' or '~/services/polls' per repo config) and drop the
file extension so the PollService reference continues to work everywhere it’s
used in this file.

In `@packages/graphql/src/services/polls.ts`:
- Around line 152-155: unlinkedElementIds may contain duplicates causing
redundant calls to recomputeDerivedPermissions and longer transactions; before
the loop in the polls service, deduplicate unlinkedElementIds (e.g., convert to
a Set or use Array.from(new Set(...))) and iterate the unique list when calling
await recomputeDerivedPermissions({ elementId }, prisma) so each elementId is
processed only once.
- Around line 9-11: The file currently uses relative imports for
Context/ContextWithUser and the helper functions; update those imports to use
the project path aliases (e.g., use @ or ~) instead of relative paths so they
follow the repository rule. Replace the imports that bring in Context and
ContextWithUser, getPermissionBooleans, and splitActivityInstances with their
aliased module paths (keep the same named symbols) so the top of the file
imports Context, ContextWithUser, getPermissionBooleans, and
splitActivityInstances via the configured alias scheme.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67ce350b-6753-4c8c-94a0-49b93e3ad5b7

📥 Commits

Reviewing files that changed from the base of the PR and between 25ad402 and d931740.

📒 Files selected for processing (2)
  • packages/graphql/src/schema/mutation.ts
  • packages/graphql/src/services/polls.ts

@cypress
Copy link
Copy Markdown

cypress bot commented Mar 15, 2026

klicker-uzh    Run #6659

Run Properties:  status check failed Failed #6659  •  git commit 4a5b568ebf ℹ️: Merge d93174077ca5f0511255550e2ebd9b4766660b87 into e50a349f953872f1f967d3467d33...
Project klicker-uzh
Branch Review poll-management
Run status status check failed Failed #6659
Run duration 08m 45s
Commit git commit 4a5b568ebf ℹ️: Merge d93174077ca5f0511255550e2ebd9b4766660b87 into e50a349f953872f1f967d3467d33...
Committer Julius Schlapbach
View all properties for this run ↗︎

Test results
Tests that failed  Failures 1
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 757
View all changes introduced in this branch ↗︎

Tests for review

Failed  cypress/e2e/O-live-quiz-workflow.cy.ts • 1 failed test

View Output Video

Test Artifacts
Different live-quiz workflows > Verify that after closing the active live quiz block, the sample solution is shown Test Replay Screenshots Video

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature size:XXL This PR changes 1000+ lines, ignoring generated files.

Development

Successfully merging this pull request may close these issues.

1 participant