Skip to content

Feat: Update and introduce new course content renderings#295

Merged
Marty-Byrde merged 25 commits into
canaryfrom
feat/introduce-content-accessibility-features
Apr 2, 2026
Merged

Feat: Update and introduce new course content renderings#295
Marty-Byrde merged 25 commits into
canaryfrom
feat/introduce-content-accessibility-features

Conversation

@Marty-Byrde

@Marty-Byrde Marty-Byrde commented Apr 1, 2026

Copy link
Copy Markdown
Member

Note

This pull request implements missing parts of the recently introduces course-content feature. It adds the new ContentSection to the OverviewSection by externalizing and redesigning the ContentSection. Additionally, it introduces a new course-content page that shows the contents of a given course, accessible through a new Show Contents item in the CourseActionMenu.

Redesigned `ContentSection` CleanShot 2026-04-01 at 10 26 25
Updated `OverviewSection` CleanShot 2026-04-01 at 10 26 50
New course content page CleanShot 2026-04-01 at 10 27 55

Summary

  • New Features

    • Added a dedicated course contents page with next/previous navigation controls
    • Added "View Contents" option to the course action menu
    • Added custom breadcrumbs to new course contents page
    • Redesigned content section UI in course creation with improved visual layout and action buttons
  • Improvements

    • Updated course card statistics to display content count
    • Added missing translation in ContentSection when section is empty

Detailed Chagenlog

Changelog

  • Features:

    • Drafted CourseContentOverview component (ba67ddd4)
      This component may be used to show the contents associated to a given check, e.g. within the OverviewSection when creating a new course.

    • Drafted /courses/contents/[courseId] page (97a6bfca)
      This new page is going to show the contents of a given course so that users can familiarize themselves with the contents associated to a given course.

    • Added "show contents" item to CourseActionMenu (57740ba5)
      This way users can view the course contents associated to a given course.

    • Redesigned ContentSection layout (510a43fd)
      This way the ContentSection now mimics the existing QuetionsSection. This means it shows the contents in a list instead of a grid. The reason for this change is that this eliminates sudden changes in the app's layout and also makes it easier to re-use this section in the OverviewSection.

    • Drafted course contents navigation panel (5248f24a)

    • Created ContentProvider component (5d0e918f)
      This component is used to manage state about the content currently shown to the user and to switch between contents.

    • Introduced ContentSwitchButton component (7028762c)
      This component is used to switch between the contents in the /contents/[courseId] page.

    • Added ContentRenderer component (9b53b647)
      This component renders the currently selected course content in a readOnly RichtTextEditor.

    • Created ContentsPageBreadcrumbs component (3ca2726c)
      This component is rendered within the course contents page to define custom breadcrumbs for this page.

    • Added ContentNavigationButtons components (a7f39785)
      These new button components allow users to continue to the next content in line or go back to the previous content.

    • Redesigned course content navigation btn appearances (f6dfcf77)
      This way the buttons show a label on top of the content title ("previous", "next") to indicate it as an call-to-action.

  • Refactors:

    • Added course contents to OverviewSection (5c228594)

    • Modularized ContentSection into subcomponents (f5a34b4d)
      Separated the rendering of the ContentSection into subcomponents to improve readability and maintainability.

    • Re-used ContentSection in OverviewSection (26bc7a18)
      By redesigning the ContentSection's layout it can be easily re-used in the OverviewSection for users to review their changes.

    • Added course content count to CourseCard (d5ff9496)
      This way the amount of contents associated to a given course are displayed when discovering new or checks a user contributes to.

    • Updated course contents page layout (45fe45c0)
      This way the navigation buttons are in the same "column" as the RichTextEditor which is visually more appealing.

    • Added editorContainerClassname arg to RichTextEditor (da8c70ae)
      This way the classes of the container wrapping the RichtTextEditor can be easily adjusted. This is especially useful to dynamically update the max-height constraint when needed.

    • Adjusted RichTextEditor height in course content page (470cb208)

    • Localized course contents page and components (d6989464)

    • Removed contents navigation on small screens (62daab8e)
      This way users on mobile screens can use the navigation buttons below the editor to navigate between contents. The reason for this is that displaying the navigation next to the editor on small screens leads to both elements not being properly visible. Similarly, showing the navigation above or below the editor reduces the overall UX and appearance.

    • Added empty course page safeguard redirect (2ca081b7)
      Previously, if users would navigate to a course page where the respective check has no contents, it would try to display contents that don't exist. This meant that the page looked kind of broken. By adding a safeguard that redirects users back to where they came from or to the courses page when the a course has no contents eliminates this edge-case.

    • Added safeguard against invalid index in ContentSBtn (b8815d02)
      The reason for this change is that when the index retrieval was unsuccessful the button previously would have switched to the invalid index (-1). Now the button is disabled when index retrieval is unsuccessful, thus when switching is not possible.

    • Added missing translation in ContentSection (1156afa5)
      Added a missing translation in the EmptyCourseContentBody subcomponent.

    • Restructured internal CreateContentButton component (70055012)
      The reason for this change is that in the wrapping div element that is the direct child of the DialogTrigger that in turn renders a button is considered the trigger. This would mean that the disabled state between button and trigger could be out-of-sync. However, the div wrapper was previously also consumed by the trigger, because it wasn't rendered. By moving the wrapping div outside the trigger, the potential issue is resolved anyhow.

This component may be used to show the contents associated to a given check, e.g. within the `OverviewSection` when creating a new course.
This new page is going to show the contents of a given course so that users can familiarize themselves with the contents associated to a given course.
This way users can view the course contents associated to a given course.
Separated the rendering of the `ContentSection` into subcomponents to improve readability and maintainability.
This way the `ContentSection` now mimics the existing `QuetionsSection`. This means it shows the contents in a list instead of a grid. The reason for this change is that this eliminates sudden changes in the app's layout and also makes it easier to re-use this section in the `OverviewSection`.
By redesigning the `ContentSection`'s layout it can be easily re-used in the `OverviewSection` for users to review their changes.
This way the amount of contents associated to a given course are displayed when discovering new or checks a user contributes to.
This component is used to manage state about the content currently shown to the user and to switch between contents.
This component is used to switch between the contents in the `/contents/[courseId]` page.
This component renders the currently selected course content in a readOnly `RichtTextEditor`.
This component is rendered within the course contents page to define custom breadcrumbs for this page.
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a course contents feature: a new localized Next.js page for course contents, context-based client components to manage and render content items (reader, navigation, switch buttons), breadcrumbs, updated course UI/actions, editor styling props, and corresponding i18n entries.

Changes

Cohort / File(s) Summary
Course Contents Page
src/app/[locale]/courses/contents/[courseId]/page.tsx, src/components/courses/contents/[courseId]/ContentsPageBreadcrumbs.tsx
New async route that fetches course by ID, handles not-found/empty redirects, renders breadcrumbs and content layout.
Content State & Context
src/components/courses/contents/[courseId]/ContentProvider.tsx
New client-side context/provider exposing currentContentIndex, setter, and contents via useContentContext().
Content Rendering & Navigation
src/components/courses/contents/[courseId]/ContentRenderer.tsx, src/components/courses/contents/[courseId]/ContentNavigationButtons.tsx, src/components/courses/contents/[courseId]/ContentSwitchButton.tsx
Added renderer using RichTextEditor (read-only), next/previous navigation buttons, and category-based switch buttons that update provider index.
Actions & Cards
src/components/courses/(hamburger-menu)/CourseActionMenu.tsx, src/components/courses/CourseCard.tsx
Action menu gains "View contents" item (disabled with tooltip when empty); CourseCard statistics adjusted to surface contents count and questions count.
Course Creation UI
src/components/courses/create/(sections)/ContentSection.tsx, src/components/courses/create/(sections)/OverviewSection.tsx
ContentSection refactored to dialog-based edit/delete, new CreateContentButton, empty-state component; OverviewSection includes ContentSection.
Editor Props
src/components/tiptap-examples/RichTextEditor.tsx
Added optional editorPaneClassname and editorContainerClassname props for container styling.
Localization
src/i18n/locales/en.json, src/i18n/locales/en.ts, src/i18n/locales/de.json, src/i18n/locales/de.ts
Added Courses.Contents namespace (title, description, navigation labels), contents_label for CourseCard statistics, and CourseActionMenu.show_course_contents entries for both English and German.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ActionMenu as CourseActionMenu
    participant Page as CourseContentsPage
    participant Provider as ContentProvider
    participant Renderer as ContentRenderer
    participant Nav as NavigationButtons
    participant Switch as ContentSwitchButtons

    User->>ActionMenu: Click "View Contents"
    ActionMenu->>Page: Navigate to /courses/contents/[id]
    Page->>Page: Fetch course by ID
    Page->>Provider: Render with initial contents & index
    Provider->>Renderer: Provide contents & currentIndex
    Provider->>Nav: Provide contents & currentIndex
    Provider->>Switch: Provide contents & currentIndex

    User->>Nav: Click Next/Previous
    Nav->>Provider: setCurrentIndex(newIndex)
    Provider->>Renderer: Update rendered content

    User->>Switch: Click category button
    Switch->>Provider: setCurrentIndex(categoryIndex)
    Provider->>Renderer: Update rendered content
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Feature, Visual Improvements, released on @canary``, Done

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% 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
Title check ✅ Passed The title accurately describes the main change: introducing a new course contents feature with redesigned rendering components and a dedicated page.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/introduce-content-accessibility-features

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.

@codecov

codecov Bot commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 42.85714% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...nents/courses/create/(sections)/ContentSection.tsx 46.15% 6 Missing and 1 partial ⚠️
...ents/courses/(hamburger-menu)/CourseActionMenu.tsx 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
src/components/courses/contents/[courseId]/ContentRenderer.tsx (1)

7-11: Consider handling empty content state.

When contents.at(currentContentIndex) returns undefined (empty contents array or out-of-bounds index), the RichTextEditor will render with no content. Consider adding a fallback UI for better user experience.

💡 Optional: Add empty state handling
 export function ContentRenderer() {
   const { contents, currentContentIndex } = useContentContext()
+  const currentContent = contents.at(currentContentIndex)
+
+  if (!currentContent) {
+    return <div className="text-center text-muted-foreground p-8">No content available</div>
+  }

-  return <RichTextEditor readOnly defaultContent={contents.at(currentContentIndex)?.content} editorPaneClassname={cn('border-dashed p-4')} />
+  return <RichTextEditor readOnly defaultContent={currentContent.content} editorPaneClassname={cn('border-dashed p-4')} />
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/courses/contents/`[courseId]/ContentRenderer.tsx around lines
7 - 11, ContentRenderer currently passes
contents.at(currentContentIndex)?.content directly to RichTextEditor which
yields an empty editor when contents is empty or index is out of bounds; update
ContentRenderer to detect when contents.at(currentContentIndex) is undefined and
render a clear empty-state fallback (e.g., a placeholder message/component or
pass an explicit empty string to RichTextEditor) and/or show an action (like "No
content available" with a button to add content) so the UI is informative;
locate the logic in the ContentRenderer component and use useContentContext,
currentContentIndex, and RichTextEditor to implement the check and conditional
rendering.
src/components/courses/contents/[courseId]/ContentsPageBreadcrumbs.tsx (1)

5-31: Hardcoded breadcrumb labels should use i18n.

The breadcrumb labels "Home", "Courses", and "Contents" are hardcoded strings, but the codebase uses next-international for localization. The existing Shared.Breadcrumbs namespace already has translations for root ("Home") and courses ("Kurse" in German).

♻️ Suggested fix using i18n
+'use client'
+
 import Link from 'next/link'
 import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@/src/components/shadcn/breadcrumb'
+import { useScopedI18n } from '@/src/i18n/client-localization'
 import { Course } from '@/src/schemas/CourseSchema'

 export function ContentPageBreadcrumbs({ course }: { course: Course }) {
+  const t = useScopedI18n('Shared.Breadcrumbs')
+
   return (
     <Breadcrumb>
       <BreadcrumbList>
         <BreadcrumbItem>
           <BreadcrumbLink asChild>
-            <Link href={`/`}>Home</Link>
+            <Link href={`/`}>{t('root')}</Link>
           </BreadcrumbLink>
         </BreadcrumbItem>
         <BreadcrumbSeparator />
         <BreadcrumbItem>
           <BreadcrumbLink asChild>
-            <Link href={`/courses`}>Courses</Link>
+            <Link href={`/courses`}>{t('courses')}</Link>
           </BreadcrumbLink>
         </BreadcrumbItem>
         <!-- ... -->
-        <BreadcrumbPage>Contents</BreadcrumbPage>
+        <BreadcrumbPage>{t('contents')}</BreadcrumbPage>
       </BreadcrumbList>
     </Breadcrumb>
   )
 }

Note: You may need to add a contents key to Shared.Breadcrumbs in your locale files if it doesn't exist.

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

In `@src/components/courses/contents/`[courseId]/ContentsPageBreadcrumbs.tsx
around lines 5 - 31, Replace hardcoded breadcrumb labels in
ContentPageBreadcrumbs with localized strings from next-international: import
and call the translation hook (e.g., useTranslations) inside
ContentPageBreadcrumbs, then use the Shared.Breadcrumbs namespace keys
(Shared.Breadcrumbs.root for "Home", Shared.Breadcrumbs.courses for "Courses")
and add/use a Shared.Breadcrumbs.contents key for "Contents" (add it to locale
files if missing). Update the Link label for the courses and the BreadcrumbPage
text to use the translated values while leaving links/paths unchanged.
src/components/courses/(hamburger-menu)/CourseActionMenu.tsx (1)

113-115: Use relative App Router navigation for internal routes.

At line 114, change router.push(${window.location.origin}/courses/contents/${id}) to router.push('/courses/contents/${id}'). According to Next.js documentation, relative paths without the origin are preferred for internal navigation—they enable route optimizations like prefetching and caching, and avoid unnecessary browser-origin coupling. The same pattern appears in other router.push calls in this file and should also be updated.

♻️ Proposed change
          onClick={() => {
-           router.push(`${window.location.origin}/courses/contents/${id}`)
+           router.push(`/courses/contents/${id}`)
          }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/courses/`(hamburger-menu)/CourseActionMenu.tsx around lines
113 - 115, The router.push call in the onClick handler inside
CourseActionMenu.tsx uses an absolute URL via window.location.origin; replace
router.push(`${window.location.origin}/courses/contents/${id}`) with a relative
route router.push(`/courses/contents/${id}`) to use Next.js App Router
optimizations, and similarly update any other router.push occurrences in this
file to use relative paths (search for router.push(...) inside CourseActionMenu
component and adjust).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[locale]/courses/contents/[courseId]/page.tsx:
- Line 23: The page currently renders ContentProvider with initialProps={{
contents: course.contents }} unconditionally, which breaks when course.contents
is empty; before returning the JSX that mounts ContentProvider (or in the page
loader), check course.contents.length and handle the empty-case server-side by
calling notFound() or returning an explicit server-rendered empty-state UI,
e.g., if (!course.contents || course.contents.length === 0) invoke notFound() or
render a simple EmptyContents component instead of mounting ContentProvider so
the route won't render a broken workspace when contents are absent.

In `@src/components/courses/contents/`[courseId]/ContentNavigationButtons.tsx:
- Around line 12-18: The component ContentNavigationButtons currently only
checks for the "next" boundary in a narrow way; add robust guards so empty lists
and invalid currentContentIndex values never render the next button or allow
out-of-range increments: return early (or render null) when contents.length ===
0, ensure you only render the next button when currentContentIndex >= 0 &&
currentContentIndex < contents.length - 1, and change the onClick updater used
with setCurrentIndex to clamp the new index (e.g., setCurrentIndex(prev =>
Math.min(prev + 1, contents.length - 1))) so clicks cannot push the index past
the end; reference variables/functions: contents, currentContentIndex,
setCurrentIndex, and component ContentNavigationButtons.

In `@src/components/courses/contents/`[courseId]/ContentSwitchButton.tsx:
- Around line 14-17: The computed index from useMemo (index) can be -1 when
contents.findIndex fails; update the ContentSwitchButton so it guards against
this by disabling the Button and preventing setCurrentIndex from being called
when index === -1. Locate the useMemo that defines index and the Button onClick
that calls setCurrentIndex(index) and change behavior: set the Button's disabled
prop (or add a conditional class) when index === -1 and make the onClick a no-op
unless index >= 0 (or early-return) so you never write an invalid index into
state; keep existing styling logic using currentContentIndex for the
active/underline state.

In `@src/components/courses/create/`(sections)/ContentSection.tsx:
- Around line 57-60: In the ContentSection component remove the invalid empty
JSX expression in the span (the "{ }" after content.title) — either delete the
entire span element or replace the empty expression with a real value (for
example a field from the content object like content.subtitle or content.meta)
so the JSX compiles; locate the div with className 'flex items-center
justify-between' containing the h2 that renders content.title and update the
adjacent span accordingly.
- Around line 98-103: The empty-state message in EmptyCourseContentBody is
hardcoded; replace the literal string with the localized string from the
component's i18n scope by calling useScopedI18n() (or using the existing scoped
`t` if already available) and render t('empty_state_label') instead of the
English text, and add the corresponding key "empty_state_label" under
Courses.Create.ContentSection in the translations files for all locales; ensure
the key name matches exactly and preserves punctuation/spacing needed for the
UI.
- Around line 54-55: The CourseContent items lack a unique id causing key and
deletion bugs: add a unique id field to the CourseContent type/schema and ensure
each created item gets a stable id; update the render in the map (where contents
is iterated and GenericCard currently uses key={i + content.categoryId}) to use
key={content.id} instead of the index-based key; and change removeCourseContent
to accept an id (or use content.id) and filter the contents array by item.id !==
id (instead of filtering by categoryId) so only the targeted item is removed.
- Around line 37-44: The wrapper <div> is currently the direct child of the
dialog trigger so it becomes the trigger and lets the dialog open even when the
Button is visually disabled; to fix, move the surrounding div outside and make
the Button (the <Button> element inside CourseContentDialog) the direct
child/trigger of CourseContentDialog (the component that renders DialogTrigger
asChild), ensuring the Button's disabled prop is respected—update the JSX so
CourseContentDialog is nested inside the outer wrapper and the <Button
disabled={disabled}> (with the <Plus /> icon) is the direct trigger element.

---

Nitpick comments:
In `@src/components/courses/`(hamburger-menu)/CourseActionMenu.tsx:
- Around line 113-115: The router.push call in the onClick handler inside
CourseActionMenu.tsx uses an absolute URL via window.location.origin; replace
router.push(`${window.location.origin}/courses/contents/${id}`) with a relative
route router.push(`/courses/contents/${id}`) to use Next.js App Router
optimizations, and similarly update any other router.push occurrences in this
file to use relative paths (search for router.push(...) inside CourseActionMenu
component and adjust).

In `@src/components/courses/contents/`[courseId]/ContentRenderer.tsx:
- Around line 7-11: ContentRenderer currently passes
contents.at(currentContentIndex)?.content directly to RichTextEditor which
yields an empty editor when contents is empty or index is out of bounds; update
ContentRenderer to detect when contents.at(currentContentIndex) is undefined and
render a clear empty-state fallback (e.g., a placeholder message/component or
pass an explicit empty string to RichTextEditor) and/or show an action (like "No
content available" with a button to add content) so the UI is informative;
locate the logic in the ContentRenderer component and use useContentContext,
currentContentIndex, and RichTextEditor to implement the check and conditional
rendering.

In `@src/components/courses/contents/`[courseId]/ContentsPageBreadcrumbs.tsx:
- Around line 5-31: Replace hardcoded breadcrumb labels in
ContentPageBreadcrumbs with localized strings from next-international: import
and call the translation hook (e.g., useTranslations) inside
ContentPageBreadcrumbs, then use the Shared.Breadcrumbs namespace keys
(Shared.Breadcrumbs.root for "Home", Shared.Breadcrumbs.courses for "Courses")
and add/use a Shared.Breadcrumbs.contents key for "Contents" (add it to locale
files if missing). Update the Link label for the courses and the BreadcrumbPage
text to use the translated values while leaving links/paths unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fe62ed82-29c9-4492-bdef-c19d3ac97b2e

📥 Commits

Reviewing files that changed from the base of the PR and between b84e8fb and 2e203c5.

📒 Files selected for processing (15)
  • src/app/[locale]/courses/contents/[courseId]/page.tsx
  • src/components/courses/(hamburger-menu)/CourseActionMenu.tsx
  • src/components/courses/CourseCard.tsx
  • src/components/courses/contents/[courseId]/ContentNavigationButtons.tsx
  • src/components/courses/contents/[courseId]/ContentProvider.tsx
  • src/components/courses/contents/[courseId]/ContentRenderer.tsx
  • src/components/courses/contents/[courseId]/ContentSwitchButton.tsx
  • src/components/courses/contents/[courseId]/ContentsPageBreadcrumbs.tsx
  • src/components/courses/create/(sections)/ContentSection.tsx
  • src/components/courses/create/(sections)/OverviewSection.tsx
  • src/components/tiptap-examples/RichTextEditor.tsx
  • src/i18n/locales/de.json
  • src/i18n/locales/de.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/en.ts

Comment thread src/app/[locale]/courses/contents/[courseId]/page.tsx
Comment thread src/components/courses/contents/[courseId]/ContentNavigationButtons.tsx Outdated
Comment thread src/components/courses/contents/[courseId]/ContentSwitchButton.tsx Outdated
Comment thread src/components/courses/create/(sections)/ContentSection.tsx Outdated
Comment thread src/components/courses/create/(sections)/ContentSection.tsx
Comment thread src/components/courses/create/(sections)/ContentSection.tsx
Comment thread src/components/courses/create/(sections)/ContentSection.tsx
@Marty-Byrde Marty-Byrde moved this to In review in KnowledgeCheckr Project Apr 1, 2026
@Marty-Byrde Marty-Byrde changed the title Feat: Introduce content accessibility features Feat: Update and introduce new course content renderings Apr 1, 2026
These new button components allow users to continue to the next content in line or go back to the previous content.
This way the navigation buttons are in the same "column" as the `RichTextEditor` which is visually more appealing.
This way the buttons show a label on top of the content title ("previous", "next") to indicate it as an call-to-action.
This way the classes of the container wrapping the `RichtTextEditor` can be easily adjusted. This is especially useful to dynamically update the max-height constraint when needed.
This way users on mobile screens can use the navigation buttons below the editor to navigate between contents. The reason for this is that displaying the navigation next to the editor on small screens leads to both elements not being properly visible. Similarly, showing the navigation above or below the editor reduces the overall UX and appearance.
Previously, if users would navigate to a course page where the respective check has no contents, it would try to display contents that don't exist. This meant that the page looked kind of broken. By adding a safeguard that redirects users back to where they came from or to the courses page when the a course has no contents eliminates this edge-case.
The reason for this change is that when the index retrieval was unsuccessful the button previously would have switched to the invalid index (-1). Now the button is disabled when index retrieval is unsuccessful, thus when switching is not possible.
Added a missing translation in the `EmptyCourseContentBody` subcomponent.
The reason for this change is that in the wrapping div element that is the direct child of the DialogTrigger that in turn renders a button is considered the trigger. This would mean that the disabled state between button and trigger could be out-of-sync. However, the div wrapper was previously also consumed by the trigger, because it wasn't rendered. By moving the wrapping div outside the trigger, the potential issue is resolved anyhow.
@Marty-Byrde Marty-Byrde force-pushed the feat/introduce-content-accessibility-features branch from 2e203c5 to 7005501 Compare April 2, 2026 05:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/components/courses/create/(sections)/ContentSection.tsx (2)

54-55: ⚠️ Potential issue | 🔴 Critical

Stop using categoryId as the content item's identity.

Lines 55 and 81 still treat categoryId as unique per content item. If two contents share a category, React keys collide and the delete handler removes both. The new reader sidebar repeats the same assumption on src/app/[locale]/courses/contents/[courseId]/page.tsx Line 47 and in src/components/courses/contents/[courseId]/ContentSwitchButton.tsx. Please add a stable content.id and key/delete/switch on that instead.

Suggested fix
-      {contents.map((content, i) => (
-        <GenericCard disableInteractions key={i + content.categoryId} className={cn('relative flex h-fit gap-3 p-2 first:mt-3 hover:bg-none')}>
+      {contents.map((content) => (
+        <GenericCard disableInteractions key={content.id} className={cn('relative flex h-fit gap-3 p-2 first:mt-3 hover:bg-none')}>
-          <ConfirmationDialog confirmAction={() => removeCourseContent(content.categoryId)} confirmLabel={t('Actions.delete_content_confirm_label')} body={t('Actions.delete_content_dialog_body')}>
+          <ConfirmationDialog confirmAction={() => removeCourseContent(content.id)} confirmLabel={t('Actions.delete_content_confirm_label')} body={t('Actions.delete_content_dialog_body')}>

Also applies to: 81-81

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

In `@src/components/courses/create/`(sections)/ContentSection.tsx around lines 54
- 55, Replace use of categoryId as the content identity with a stable unique
content.id: update the map in ContentSection (the GenericCard key and any delete
handler invocation inside that map) to key on content.id instead of i +
content.categoryId, and change the delete handler to accept and use content.id;
likewise update the reader sidebar page (page.tsx) and ContentSwitchButton to
use content.id for keys and switch/delete operations so items with the same
categoryId no longer collide. Ensure all references to categoryId for identity
are preserved only for category semantics, not for React keys or item
operations.

57-60: ⚠️ Potential issue | 🔴 Critical

Remove the empty JSX expression.

Line 59's {} is invalid TSX and will fail the build. Drop the span or render a real value.

Suggested fix
             <div className='flex items-center justify-between'>
               <h2 className='text-neutral-700 dark:text-neutral-300'>{content.title}</h2>
-              <span className='text-neutral-700 dark:text-neutral-300'>{}</span>
             </div>

Run this to confirm the empty JSX expression is still present:

#!/bin/bash
sed -n '57,60p' "src/components/courses/create/(sections)/ContentSection.tsx"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/courses/create/`(sections)/ContentSection.tsx around lines 57
- 60, Remove the invalid empty JSX expression in the ContentSection render: the
span containing `{}` should be deleted or replaced with a real value; update the
JSX inside the component that renders content.title (the <div className='flex
items-center justify-between'> block in ContentSection) to either remove the
empty <span> or render a concrete property (e.g., content.someField) instead so
the TSX builds cleanly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/courses/create/`(sections)/ContentSection.tsx:
- Around line 38-42: The create trigger Button inside CourseContentDialog
(mode='create') is missing an explicit type and should be set to type='button'
to match the edit/delete triggers and avoid accidental form submissions; update
the Button element used as the create trigger (the one rendering <Plus
className='size-5' /> and t('Actions.create_new_button_label')) to include
type='button'.

---

Duplicate comments:
In `@src/components/courses/create/`(sections)/ContentSection.tsx:
- Around line 54-55: Replace use of categoryId as the content identity with a
stable unique content.id: update the map in ContentSection (the GenericCard key
and any delete handler invocation inside that map) to key on content.id instead
of i + content.categoryId, and change the delete handler to accept and use
content.id; likewise update the reader sidebar page (page.tsx) and
ContentSwitchButton to use content.id for keys and switch/delete operations so
items with the same categoryId no longer collide. Ensure all references to
categoryId for identity are preserved only for category semantics, not for React
keys or item operations.
- Around line 57-60: Remove the invalid empty JSX expression in the
ContentSection render: the span containing `{}` should be deleted or replaced
with a real value; update the JSX inside the component that renders
content.title (the <div className='flex items-center justify-between'> block in
ContentSection) to either remove the empty <span> or render a concrete property
(e.g., content.someField) instead so the TSX builds cleanly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9f086c4d-1bad-4da7-856a-4c837dd349c0

📥 Commits

Reviewing files that changed from the base of the PR and between 2e203c5 and 7005501.

📒 Files selected for processing (9)
  • src/app/[locale]/courses/contents/[courseId]/page.tsx
  • src/components/courses/contents/[courseId]/ContentNavigationButtons.tsx
  • src/components/courses/contents/[courseId]/ContentSwitchButton.tsx
  • src/components/courses/create/(sections)/ContentSection.tsx
  • src/components/tiptap-examples/RichTextEditor.tsx
  • src/i18n/locales/de.json
  • src/i18n/locales/de.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/en.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/components/courses/contents/[courseId]/ContentNavigationButtons.tsx
  • src/components/courses/contents/[courseId]/ContentSwitchButton.tsx
  • src/i18n/locales/de.ts
  • src/i18n/locales/en.ts
  • src/i18n/locales/en.json
  • src/i18n/locales/de.json

Comment thread src/components/courses/create/(sections)/ContentSection.tsx
@Marty-Byrde Marty-Byrde moved this from In review to Done in KnowledgeCheckr Project Apr 2, 2026
@Marty-Byrde Marty-Byrde added the Done Pull request is ready to be merged label Apr 2, 2026
@Marty-Byrde Marty-Byrde merged commit a567fce into canary Apr 2, 2026
31 checks passed
@Marty-Byrde Marty-Byrde deleted the feat/introduce-content-accessibility-features branch April 2, 2026 05:28
@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 4.0.0-canary.4 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Done Pull request is ready to be merged released on @canary

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant