Repository: ls1intum/Artemis
Pull request #10758: `General`: Fix sidebar and quick actions on smaller screens
Author: login: tobias-lippert
Labels: name: client
description: Pull requests that update TypeScript code. (Added Automatically!)
name: ready to merge
name: core
description: Pull requests that affect the corresponding module
Created: 2025-04-27T20:07:25Z | Merged: 2025-05-03T16:38:16Z

<!-- Thanks for contributing to Artemis! Before you submit your pull request, please make sure to check all tasks by putting an x in the [ ] (don't: [x ], [ x], do: [x]). Remove not applicable tasks and do not leave them unchecked -->
<!-- If your pull request is not ready for review yet, create a draft pull request! -->

### Checklist
#### General
<!-- Remove tasks that are not applicable for your PR. Please only put the PR into ready for review, if all relevant tasks are checked! -->
<!-- You only need to choose one of the first two check items: Generally, test on the test servers. -->
<!-- If it's only a small change, testing it locally is acceptable, and you may remove the first checkmark. If you are unsure, please test on the test servers. -->
- [x] I tested **all** changes and their related features with **all** corresponding user types on a test server.
- [x] I chose a title conforming to the [naming conventions for pull requests](https://docs.artemis.cit.tum.de/dev/development-process/development-process.html#naming-conventions-for-github-pull-requests).




#### Client
- [x] I **strictly** followed the [client coding and design guidelines](https://docs.artemis.cit.tum.de/dev/guidelines/client/).
- [x] I added multiple integration tests (Jest) related to the features (with a high test coverage), while following the [test guidelines](https://docs.artemis.cit.tum.de/dev/guidelines/client-tests/).
- [x] I added multiple screenshots/screencasts of my UI changes.



### Motivation and Context
<!-- Why is this change required? What problem does it solve? -->
<!-- If it fixes an open issue, please link to the issue here. -->
The sidebar shows some bugs on smaller screens.

- The settings item becomes part of the more dropdown
- The collapse icon is gone as soon as the settings icon becomes part of the more dropdown
- When no settings exist the collapse icon is even shown when you cannot expand the sidebar due to insufficient width
- On small screens  some unnecessary whitespace pushed more items into the more dropdown than necessary
- The quick actions wrapped items although sufficient width was available.

### Description
<!-- Describe your changes in detail -->

- We now listen to breakpoint changes and only show the collapse icon when the screen has sufficient width
- settings now never become part of the more dropdown but stay always out of it
- The collapse icon is shown as long as sufficient width exists


### Steps for Testing
<!-- Please describe in detail how reviewers can test your changes. Make sure to take all related features and views into account! Below is an example that you can refine. -->
Prerequisites:
- 1 Tutor/Editor
- 1 Instructor

1. Log in to Artemis
2. Navigate to Course Administration
3. Go to a course
4. Try out the course management view with different screen/browser window sizes
5. As long as sufficient width (960px) are available, the sidebar should be expandable and the collapse/expand icon should be always available.
6. When the height of the window reduces, settings should not become part of the more dropdown
7. Quick actions card should just keep a fixed width as soon as it has sufficient width

### Testserver States
You can manage test servers using [Helios](https://helios.aet.cit.tum.de/). Check environment statuses in the [environment list](https://helios.aet.cit.tum.de/repo/69562331/environment/list). To deploy to a test server, go to the [CI/CD](https://helios.aet.cit.tum.de/repo/69562331/ci-cd) page, find your PR or branch, and trigger the deployment.

### Review Progress
<!-- Each PR should be reviewed by at least two other developers. The code, the functionality (= manual test) and the exam mode need to be reviewed. -->
<!-- The reviewer or author check the following boxes depending on what was reviewed or tested. All boxes should be checked before merge. -->
<!-- You can add additional checkboxes if it makes sense to only review parts of the code or functionality. -->
<!-- When changes are pushed, uncheck the affected boxes. (Not all changes require full re-reviews.) -->
<!-- All PRs that might affect the exam mode (e.g. change a client component that is also used in the exam mode) need an additional verification that the exam mode still works. -->

#### Code Review
- [x] Code Review 1
- [x] Code Review 2
#### Manual Tests
- [x] Test 1
- [x] Test 2

### Test Coverage
<!-- Please add the test coverages for all changed files modified in this PR here. You can use `supporting_script/code-coverage/generate_code_cov_table/generate_code_cov_table.py` to automatically generate the coverage table from the corresponding artefacts of your branch (follow the ReadMe for setup details). -->
<!-- Alternatively you can execute the tests locally (see build.gradle and package.json) or look into the corresponding artefacts. -->
<!-- The line coverage must be above 90% for changes files, and you must use extensive and useful assertions for server tests and expect statements for client tests. -->
<!-- Note: Confirm in the last column that you have implemented extensive assertions for server tests and expect statements for client tests. -->
<!--       Remove rows with only trivial changes from the table. -->
<!--
| Class/File | Line Coverage | Confirmation (assert/expect) |
|------------|--------------:|-----------------------------:|
| ExerciseService.java | 85% | ✅                           |
| programming-exercise.component.ts | 95% | ✅              |
-->

| Class/File | Line Coverage | Confirmation (assert/expect) |
|------------|--------------:|---------------------:|
| course-sidebar.component.ts | 98.43% | ✅  |


### Screenshots
<!-- Add screenshots to demonstrate the changes in the UI. Remove the section if you did not change the UI. -->
<!-- Create a GIF file from a screen recording in a docker container https://toub.es/2017/09/11/high-quality-gif-with-ffmpeg-and-docker/


 -->

https://github.com/user-attachments/assets/54dd196c-3377-41da-86e7-9e7a79171c67


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
  - Introduced a new responsive breakpoint to enhance sidebar expandability on intermediate screen sizes.
  - Sidebar now adapts its expand/collapse toggle visibility based on layout breakpoints for improved responsiveness.

- **Bug Fixes**
  - Adjusted sidebar item hiding logic to ensure bottom-positioned items are never hidden.

- **Style**
  - Updated card layout in quick actions for more consistent sizing across devices.

- **Tests**
  - Added tests to verify sidebar behavior in response to layout breakpoint changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html b/src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html
index bf9e213bf636..647121c1f495 100644
--- a/src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html
+++ b/src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html
@@ -1,4 +1,4 @@
-<div class="card col-12 col-md-10 col-lg-8 col-xl-5 col-xxl-5 mb-3">
+<div class="card mb-3" style="max-width: 35rem; width: 100%">
     <h2 class="card-header course-quick-actions-title mb-3" jhiTranslate="entity.quickActions"></h2>
     <div class="card-body">
         <div class="d-flex flex-wrap align-items-center mb-3">
diff --git a/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html b/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html
index 067f3775a6c5..381ea10abe76 100644
--- a/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html
+++ b/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html
@@ -116,7 +116,7 @@
                                 }
                             </div>
                         </a>
-                        @if (isLast && !isNavbarCollapsed()) {
+                        @if (isLast && !isNavbarCollapsed() && canExpand()) {
                             <div
                                 class="double-arrow px-2"
                                 [ngClass]="{ 'menu-closed': isNavbarCollapsed() }"
@@ -135,7 +135,7 @@
                 </li>
             }
 
-            @if (isNavbarCollapsed() || !sidebarItemsBottom().length) {
+            @if ((isNavbarCollapsed() || !sidebarItemsBottom().length) && canExpand()) {
                 <div
                     class="double-arrow mb-2"
                     [ngClass]="{ 'menu-closed': isNavbarCollapsed() }"
diff --git a/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts b/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts
index c3d2ebdf6832..06c276675dba 100644
--- a/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts
+++ b/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts
@@ -15,14 +15,19 @@ import { CourseActionItem, CourseSidebarComponent, SidebarItem } from 'app/core/
 import { TranslateDirective } from 'app/shared/language/translate.directive';
 import { SecuredImageComponent } from 'app/shared/image/secured-image.component';
 import { FeatureToggleHideDirective } from 'app/shared/feature-toggle/feature-toggle-hide.directive';
-import { SimpleChange } from '@angular/core';
+import { SimpleChange, signal } from '@angular/core';
 import { MockActivatedRoute } from 'test/helpers/mocks/activated-route/mock-activated-route';
 import { MockTranslateService } from 'test/helpers/mocks/service/mock-translate.service';
 import { Course, CourseInformationSharingConfiguration } from 'app/core/course/shared/entities/course.model';
+import { LayoutService } from 'app/shared/breakpoints/layout.service';
+import { CustomBreakpointNames } from 'app/shared/breakpoints/breakpoints.service';
+import { BehaviorSubject } from 'rxjs';
 
 describe('CourseSidebarComponent', () => {
     let component: CourseSidebarComponent;
     let fixture: ComponentFixture<CourseSidebarComponent>;
+    let layoutService: LayoutService;
+    let breakpointsSubject: BehaviorSubject<string[]>;
 
     const course1: Course = {
         id: 1,
@@ -114,6 +119,11 @@ describe('CourseSidebarComponent', () => {
             ],
         }).compileComponents();
 
+        // we have to set this up before we create the component because the signal is set up in the constructor
+        layoutService = TestBed.inject(LayoutService);
+        breakpointsSubject = new BehaviorSubject<string[]>([]);
+        jest.spyOn(layoutService, 'subscribeToLayoutChanges').mockReturnValue(breakpointsSubject.asObservable());
+
         fixture = TestBed.createComponent(CourseSidebarComponent);
         component = fixture.componentInstance;
 
@@ -125,6 +135,25 @@ describe('CourseSidebarComponent', () => {
         fixture.detectChanges();
     });
 
+    it('should update canExpand when activeBreakpoints changes', () => {
+        expect(component.activeBreakpoints()).toEqual([]);
+        expect(component.canExpand()).toBeFalse();
+        layoutService.activeBreakpoints = [CustomBreakpointNames.sidebarExpandable];
+        fixture.detectChanges();
+        const isBreakpointActiveSpy = jest.spyOn(layoutService, 'isBreakpointActive');
+
+        breakpointsSubject.next([CustomBreakpointNames.sidebarExpandable]);
+        fixture.detectChanges();
+
+        expect(isBreakpointActiveSpy).toHaveBeenCalledExactlyOnceWith(CustomBreakpointNames.sidebarExpandable);
+        expect(component.canExpand()).toBeTrue();
+
+        layoutService.activeBreakpoints = [CustomBreakpointNames.small];
+        breakpointsSubject.next([CustomBreakpointNames.small]);
+        fixture.detectChanges();
+        expect(component.canExpand()).toBeFalse();
+    });
+
     it('should initialize visible/hidden items on  sidebar update', () => {
         const updateVisibleNavbarItemsSpy = jest.spyOn(component, 'updateVisibleNavbarItems');
         component.ngOnChanges({ sidebarItems: new SimpleChange([], mockSidebarItems, true) });
@@ -159,8 +188,8 @@ describe('CourseSidebarComponent', () => {
     it('should calculate threshold based on sidebar items length', () => {
         const threshold = component.calculateThreshold();
 
-        // WINDOW_OFFSET = 300, ITEM_HEIGHT = 38, sidebarItems.length = 5
-        const expectedThreshold = 300 + 5 * 38;
+        // WINDOW_OFFSET = 225, ITEM_HEIGHT = 38, sidebarItems.length = 5
+        const expectedThreshold = 225 + 5 * 38;
 
         expect(threshold).toBe(expectedThreshold);
     });
@@ -219,6 +248,7 @@ describe('CourseSidebarComponent', () => {
 
     it('should emit toggleCollapseState when collapse chevron is clicked', () => {
         const toggleCollapseStateSpy = jest.spyOn(component.toggleCollapseState, 'emit');
+        component.canExpand = signal(true);
         fixture.detectChanges();
         const collapseButton = fixture.debugElement.query(By.css('.double-arrow'));
         expect(collapseButton).toBeTruthy();
diff --git a/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts b/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts
index b64c4e658e2a..d8e546eebbf2 100644
--- a/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts
+++ b/src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts
@@ -1,4 +1,4 @@
-import { Component, HostListener, OnChanges, SimpleChanges, ViewChild, input, output, signal } from '@angular/core';
+import { Component, HostListener, OnChanges, Signal, SimpleChanges, ViewChild, computed, inject, input, output, signal } from '@angular/core';
 import { IconDefinition, faChevronRight, faCog, faEllipsis } from '@fortawesome/free-solid-svg-icons';
 import { NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle, NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
 import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service';
@@ -9,6 +9,9 @@ import { SecuredImageComponent } from 'app/shared/image/secured-image.component'
 import { RouterLink, RouterLinkActive } from '@angular/router';
 import { FeatureToggleHideDirective } from 'app/shared/feature-toggle/feature-toggle-hide.directive';
 import { Course } from 'app/core/course/shared/entities/course.model';
+import { LayoutService } from 'app/shared/breakpoints/layout.service';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { CustomBreakpointNames } from 'app/shared/breakpoints/breakpoints.service';
 
 export interface CourseActionItem {
     title: string;
@@ -51,11 +54,14 @@ export interface SidebarItem {
     ],
 })
 export class CourseSidebarComponent implements OnChanges {
+    protected readonly faChevronRight = faChevronRight;
+    protected readonly faEllipsis = faEllipsis;
+    protected readonly faCog = faCog;
+
     course = input<Course | undefined>();
     courses = input<Course[] | undefined>();
     sidebarItemsTop = signal<SidebarItem[]>([]);
     sidebarItemsBottom = signal<SidebarItem[]>([]);
-    lastItemBottom = signal<SidebarItem | undefined>(undefined);
     sidebarItems = input<SidebarItem[]>([]);
     courseActionItems = input<CourseActionItem[]>([]);
     isNavbarCollapsed = input<boolean>(false);
@@ -64,6 +70,7 @@ export class CourseSidebarComponent implements OnChanges {
     isTestServer = input<boolean>(false);
     hasUnreadMessages = input<boolean>(false);
     communicationRouteLoaded = input<boolean>(false);
+    layoutService = inject(LayoutService);
 
     hiddenItems = signal<SidebarItem[]>([]);
     anyItemHidden = signal<boolean>(false);
@@ -71,17 +78,22 @@ export class CourseSidebarComponent implements OnChanges {
     switchCourse = output<Course>();
     courseActionItemClick = output<CourseActionItem>();
     toggleCollapseState = output<void>();
+    activeBreakpoints: Signal<string[]>;
+    canExpand: Signal<boolean>;
 
     @ViewChild('itemsDrop') itemsDrop!: NgbDropdown;
 
     // Constants for threshold calculation
-    readonly WINDOW_OFFSET: number = 300;
+    readonly WINDOW_OFFSET: number = 225;
     readonly ITEM_HEIGHT: number = 38;
 
-    // Icons
-    faChevronRight = faChevronRight;
-    faEllipsis = faEllipsis;
-    protected readonly faCog = faCog;
+    constructor() {
+        this.activeBreakpoints = toSignal(this.layoutService.subscribeToLayoutChanges(), { initialValue: [] as string[] });
+        this.canExpand = computed(() => {
+            this.activeBreakpoints();
+            return this.layoutService.isBreakpointActive(CustomBreakpointNames.sidebarExpandable);
+        });
+    }
 
     ngOnChanges(changes: SimpleChanges): void {
         if (changes['sidebarItems']) {
@@ -112,7 +124,7 @@ export class CourseSidebarComponent implements OnChanges {
         const newHiddenItems: SidebarItem[] = [];
         let newAnyItemHidden = false;
         if (this.sidebarItems()) {
-            const reversedSidebarItems = [...this.sidebarItems()].reverse();
+            const reversedSidebarItems = [...this.sidebarItems()].filter((item) => !item.bottom).reverse();
 
             reversedSidebarItems.forEach((item, index) => {
                 const currentThreshold = threshold - index * this.ITEM_HEIGHT;
@@ -135,6 +147,4 @@ export class CourseSidebarComponent implements OnChanges {
         }
         return this.sidebarItems().length * this.ITEM_HEIGHT + this.WINDOW_OFFSET;
     }
-
-    protected readonly FeatureToggle = FeatureToggle;
 }
diff --git a/src/main/webapp/app/shared/breakpoints/breakpoints.service.ts b/src/main/webapp/app/shared/breakpoints/breakpoints.service.ts
index 92c46702e29d..2b6e48f7c3d2 100644
--- a/src/main/webapp/app/shared/breakpoints/breakpoints.service.ts
+++ b/src/main/webapp/app/shared/breakpoints/breakpoints.service.ts
@@ -5,6 +5,7 @@ export const CustomBreakpointNames = {
     small: 'small',
     medium: 'medium',
     large: 'large',
+    sidebarExpandable: 'sidebarExpandable',
     extraLarge: 'extraLarge',
 };
 
@@ -18,6 +19,7 @@ export class BreakpointsService {
         this.breakpoints.set('(max-width: 576px)', CustomBreakpointNames.extraSmall);
         this.breakpoints.set('(min-width: 576px)', CustomBreakpointNames.small);
         this.breakpoints.set('(min-width: 768px)', CustomBreakpointNames.medium);
+        this.breakpoints.set('(min-width: 960px)', CustomBreakpointNames.sidebarExpandable);
         this.breakpoints.set('(min-width: 992px)', CustomBreakpointNames.large);
         this.breakpoints.set('(min-width: 1200px)', CustomBreakpointNames.extraLarge);
     }


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

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

<details>
<summary>src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts (1)</summary><blockquote>

`57-60`: **Consistent access level for icon constants**

The icon constants were switched to `protected readonly`, which is great.  
Consider doing the same for all other constant-like fields in the class (e.g. `WINDOW_OFFSET`, `ITEM_HEIGHT`) for consistency and to prevent mutation.

</blockquote></details>

</blockquote></details>

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

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

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

Reviewing files that changed from the base of the PR and between b1b5cd7180bc3800dd6cc11b23dc131a4a963ed3 and 2e6ab5bbbd1a5c55fc5d16e4c3c14d18e583ea32.

</details>

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

* `src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html` (1 hunks)
* `src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html` (2 hunks)
* `src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts` (3 hunks)
* `src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts` (5 hunks)
* `src/main/webapp/app/shared/breakpoints/breakpoints.service.ts` (2 hunks)

</details>

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

<details>
<summary>📓 Path-based instructions (2)</summary>

<details>
<summary>`src/main/webapp/**/*.html`: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.</summary>

> `src/main/webapp/**/*.html`: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

- `src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html`
- `src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html`

</details>
<details>
<summary>`src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalC...</summary>

> `src/main/webapp/**/*.ts`: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

- `src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts`
- `src/main/webapp/app/shared/breakpoints/breakpoints.service.ts`
- `src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts`

</details>

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

<details>
<summary>src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts (1)</summary><blockquote>

<details>
<summary>src/main/webapp/app/shared/breakpoints/breakpoints.service.ts (1)</summary>

* `CustomBreakpointNames` (3-10)

</details>

</blockquote></details>
<details>
<summary>src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts (1)</summary><blockquote>

<details>
<summary>src/main/webapp/app/shared/breakpoints/breakpoints.service.ts (1)</summary>

* `CustomBreakpointNames` (3-10)

</details>

</blockquote></details>

</details>

</details>

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

* GitHub Check: Codacy Static Code Analysis
* GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
* GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
* GitHub Check: Build .war artifact
* GitHub Check: server-style
* GitHub Check: client-style
* GitHub Check: client-tests
* GitHub Check: server-tests
* GitHub Check: Analyse

</details>

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

<details>
<summary>src/main/webapp/app/shared/breakpoints/breakpoints.service.ts (1)</summary>

`8-8`: **The new breakpoint implementation looks good**

Adding the custom breakpoint `sidebarExpandable` at 960px creates an intermediate breakpoint between medium (768px) and large (992px), which is appropriate for conditionally controlling the sidebar's expandability.



Also applies to: 22-22

</details>
<details>
<summary>src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html (1)</summary>

`1-1`: **Well-implemented fixed width approach**

Replacing the Bootstrap grid classes with explicit CSS styles (`max-width: 35rem; width: 100%`) helps address the issue of unnecessary whitespace causing items to be pushed into the "more" dropdown on small screens. This fixed width approach provides more predictable behavior across different screen sizes.

</details>
<details>
<summary>src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html (2)</summary>

`119-119`: **Good conditional rendering of collapse icon**

Adding the `canExpand()` condition ensures the double-arrow is only shown when the sidebar can actually be expanded, directly addressing one of the issues mentioned in the PR objectives.

---

`138-138`: **Properly implemented responsive behavior**

This change ensures the collapse icon is hidden when there isn't enough screen width to support it, consistent with the PR objectives and providing a better user experience on smaller screens.

</details>
<details>
<summary>src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts (2)</summary>

`22-24`: **Good test setup for breakpoint responsiveness**

Properly importing and setting up the necessary dependencies for testing the breakpoint-based behavior.



Also applies to: 29-30, 123-126

---

`135-152`: **Well-structured test for the responsive behavior**

The test thoroughly verifies the component's reactive behavior with respect to breakpoints:
- It checks the initial state of signals
- It verifies `canExpand` becomes true when the `sidebarExpandable` breakpoint is active
- It confirms `canExpand` becomes false when the breakpoint is no longer active

This ensures the sidebar's expandability correctly responds to screen size changes.

</details>
<details>
<summary>src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts (1)</summary>

`128-128`: **Good catch on excluding `bottom` items from the hide-list**

Filtering `bottom` items before reversing fixes the earlier bug where footer items could disappear from the sidebar.  
Nice improvement!

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-27T20:11:08Z
user: login: coderabbitai[bot]
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T13:02:59Z
user: login: coderabbitai[bot]
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T21:20:51Z
user: login: raffifasaro
body: Code LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T22:10:56Z
user: login: HawKhiem
body: Tested on TS3. Works as described

![image](https://github.com/user-attachments/assets/c56a5488-e147-46b1-a8e0-6e248a7d661e)

state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T09:53:49Z
user: login: Anishyou
body: Locally tested, Works as described
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T10:14:18Z
user: login: florian-glombik
body: Code

I left some comments for improvements for future PRs, nothing deal breaking but I think that the code could be written a little more clean and easier to grasp - so more for future PRs (I guess the comments are a little opinionated anyways)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T14:59:03Z
user: login: timn21
body: Tested on TS3, works as described.
![image](https://github.com/user-attachments/assets/202073d8-452f-4914-b7c7-40ada1bf6366)

state: APPROVED
author_association: NONE
submitted_at: 2025-05-02T15:39:01Z
user: login: krusche
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-03T16:38:06Z

Review comments:
diff_hunk: @@ -64,24 +70,31 @@ export class CourseSidebarComponent implements OnChanges {
     isTestServer = input<boolean>(false);
     hasUnreadMessages = input<boolean>(false);
     communicationRouteLoaded = input<boolean>(false);
+    layoutService = inject(LayoutService);
 
     hiddenItems = signal<SidebarItem[]>([]);
     anyItemHidden = signal<boolean>(false);
 
     switchCourse = output<Course>();
     courseActionItemClick = output<CourseActionItem>();
     toggleCollapseState = output<void>();
+    activeBreakpoints: Signal<string[]>;
+    canExpand: Signal<boolean>;
 
path: src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Mark injected & computed signals as `readonly` and satisfy strict-property-initialization**

The two class fields `layoutService`, `activeBreakpoints`, and `canExpand` are currently
 • public (no access-modifier)  
 • non-`readonly`, which allows accidental reassignment, and  
 • not definitely assigned (`activeBreakpoints` / `canExpand`) until the constructor runs, leading to a compilation error when `strictPropertyInitialization` is enabled (Angular CLI enables it in `strict` mode).

A concise fix is to (a) give the members the appropriate visibility, (b) mark them `readonly`, and (c) initialise the signals directly at declaration time so the constructor can be simplified or removed:

```diff
-    layoutService = inject(LayoutService);
-
-    activeBreakpoints: Signal<string[]>;
-    canExpand: Signal<boolean>;
+    protected readonly layoutService = inject(LayoutService);
+
+    protected readonly activeBreakpoints: Signal<string[]> = toSignal(
+        this.layoutService.subscribeToLayoutChanges(),
+        { initialValue: [] as string[] },
+    );
+
+    /** `true` when the custom `sidebarExpandable` breakpoint is active. */
+    protected readonly canExpand: Signal<boolean> = computed(() =>
+        this.layoutService.isBreakpointActive(CustomBreakpointNames.sidebarExpandable),
+    );
```

If you keep the constructor, it can now be removed or reduced to an empty body because the fields are fully initialised above.  
This change removes the risk of accidental mutation and guarantees the code compiles under `strict` settings.  
 


Also applies to: 90-97

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-27T20:11:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,24 +70,30 @@ export class CourseSidebarComponent implements OnChanges {
     isTestServer = input<boolean>(false);
     hasUnreadMessages = input<boolean>(false);
     communicationRouteLoaded = input<boolean>(false);
+    layoutService = inject(LayoutService);
 
     hiddenItems = signal<SidebarItem[]>([]);
     anyItemHidden = signal<boolean>(false);
 
     switchCourse = output<Course>();
     courseActionItemClick = output<CourseActionItem>();
     toggleCollapseState = output<void>();
+    activeBreakpoints: Signal<string[]>;
+    canExpand: Signal<boolean>;
path: src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts
user: login: florian-glombik
body: Why don't you directly asssign the computed signal here instead of in the constructor?

I think it is better to define it here, or am I missing some Angular guidelines?
created_at: 2025-05-02T14:54:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,24 +70,30 @@ export class CourseSidebarComponent implements OnChanges {
     isTestServer = input<boolean>(false);
     hasUnreadMessages = input<boolean>(false);
     communicationRouteLoaded = input<boolean>(false);
+    layoutService = inject(LayoutService);
 
     hiddenItems = signal<SidebarItem[]>([]);
     anyItemHidden = signal<boolean>(false);
 
     switchCourse = output<Course>();
     courseActionItemClick = output<CourseActionItem>();
     toggleCollapseState = output<void>();
+    activeBreakpoints: Signal<string[]>;
+    canExpand: Signal<boolean>;
 
     @ViewChild('itemsDrop') itemsDrop!: NgbDropdown;
 
     // Constants for threshold calculation
-    readonly WINDOW_OFFSET: number = 300;
+    readonly WINDOW_OFFSET: number = 225;
path: src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts
user: login: florian-glombik
body: Could you please add a comment how you get to the 225 offset?
created_at: 2025-05-02T14:54:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,7 +135,7 @@
                 </li>
             }
 
-            @if (isNavbarCollapsed() || !sidebarItemsBottom().length) {
+            @if ((isNavbarCollapsed() || !sidebarItemsBottom().length) && canExpand()) {
path: src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html
user: login: florian-glombik
body: This condition is quite complex, a computed signal with a proper name would be better
created_at: 2025-05-02T14:56:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,7 +116,7 @@
                                 }
                             </div>
                         </a>
-                        @if (isLast && !isNavbarCollapsed()) {
+                        @if (isLast && !isNavbarCollapsed() && canExpand()) {
path: src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html
user: login: florian-glombik
body: Also here I cannot immediately grasp the condition in real words, a computed signal with a proper name would be a more clean way 
created_at: 2025-05-02T14:57:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-27T20:11:04Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

```markdown
## Walkthrough

The changes introduce a new custom layout breakpoint (`sidebarExpandable`) and integrate responsive layout signals into the course sidebar component. The sidebar now conditionally displays its expand/collapse arrow based on the new breakpoint, and the logic for hiding sidebar items excludes bottom-positioned items. The quick-actions component's layout is updated to use explicit CSS width constraints instead of Bootstrap grid classes. Tests are added to verify the sidebar's responsive behavior. The breakpoints service is extended to support the new custom breakpoint.

## Changes

| File(s)                                                                                     | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html           | Replaced Bootstrap grid classes with fixed max-width and width styles on the container div, altering the card's sizing and responsiveness.                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.html         | Updated conditional rendering for the expand/collapse arrow to require `canExpand()`, restricting its display to cases where expansion is allowed.                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.ts           | Added Angular signals for layout breakpoint awareness, injected `LayoutService`, and introduced `canExpand` logic. Refined icon property declarations, lowered the sidebar threshold offset, and modified item hiding logic to exclude bottom-positioned items from being hidden.                                                                                                                                                                                                                                                                                                                                  |
| src/main/webapp/app/core/course/shared/course-sidebar/course-sidebar.component.spec.ts      | Added a test to verify that the sidebar component's `canExpand` signal updates correctly in response to layout breakpoint changes, ensuring proper initialization and reactivity.                                                                                                                                                                                                                                                                                                                                                                                             |
| src/main/webapp/app/shared/breakpoints/breakpoints.service.ts                              | Added the `sidebarExpandable` custom breakpoint at `(min-width: 960px)` to the breakpoints service and its exported constants.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant SidebarComponent
    participant LayoutService
    participant BreakpointsService

    User->>SidebarComponent: Loads sidebar
    SidebarComponent->>LayoutService: Subscribe to layout changes
    LayoutService->>BreakpointsService: Get current breakpoints
    BreakpointsService-->>LayoutService: Return breakpoints array
    LayoutService-->>SidebarComponent: Emit active breakpoints
    SidebarComponent->>SidebarComponent: Compute canExpand signal
    SidebarComponent-->>User: Render expand/collapse arrow if canExpand is true
```

## Possibly related PRs

- [ls1intum/Artemis#10749](https://github.com/ls1intum/Artemis/pull/10749): Modifies the quick-actions component's container layout, similar to the changes in this PR.
- [ls1intum/Artemis#10657](https://github.com/ls1intum/Artemis/pull/10657): Enhances the course sidebar with new bottom-positioned items and reactive signals, related to sidebar restructuring.
- [ls1intum/Artemis#10455](https://github.com/ls1intum/Artemis/pull/10455): Introduces the initial creation and integration of the `CourseSidebarComponent`, which is directly enhanced in this PR.

## Suggested reviewers

- krusche
- florian-glombik
- HawKhiem
```

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

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

Reviewing files that changed from the base of the PR and between 07bb5e3be80303821a6b277a8e28f155ed6e9493 and ae2ebd5d154c0cd13ddeb1090552e05ca7bd69be.

</details>

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

* `src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html` (1 hunks)

</details>

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

* src/main/webapp/app/core/course/manage/quick-actions/quick-actions.component.html

</details>

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

* GitHub Check: Codacy Static Code Analysis
* GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
* GitHub Check: Build and Push Docker Image / Build Docker Image for ls1intum/artemis
* GitHub Check: Build .war artifact
* GitHub Check: client-tests-selected
* GitHub Check: server-tests
* GitHub Check: client-tests
* GitHub Check: server-style
* GitHub Check: client-style
* GitHub Check: Analyse

</details>

</details>
<!-- internal state start -->


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

<!-- internal state end -->
<!-- tips_start -->

---

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

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

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

</details>

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

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ls1intum/Artemis&utm_content=10758):

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

### Support

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

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

### CodeRabbit Commands (Invoked using PR comments)

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

### Other keywords and placeholders

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

### Documentation and Community

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

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-27T20:44:08Z
body: ### End-to-End (E2E) Test Results Summary

<table><tr><th><th>Tests</th><th>Passed ☑️</th><th>Skipped ⚠️</th><th>Failed ❌️</th><th>Time ⏱</th></tr><tr><td>End-to-End (E2E) Test Report</td><td>201 ran</td><td>195 passed</td><td>3 skipped</td><td>3 failed</td><td>47m 8s 49ms</td></tr></table> <table><tr><th>Test</th><th>Result</th><th>Time ⏱</th></tr><tr><td colspan="3"><strong>End-to-End (E2E) Test Report</strong></td></tr><tr><td colspan="3"><em>e2e/course/CourseMessages.spec.ts</em></td></tr><tr><td>ts.Course messages › Channel messages › Write/edit/delete message in channel › Student should be able to edit message in channel</td><td>❌ failure</td><td>2m 3s 977ms</td></tr><tr><td colspan="3"><em>e2e/exercise/quiz-exercise/QuizExerciseManagement.spec.ts</em></td></tr><tr><td>ts.Quiz Exercise Management › Quiz Exercise Creation › Creates a Quiz with Drag and Drop</td><td>❌ failure</td><td>2m 1s 992ms</td></tr><tr><td colspan="3"><em>e2e/exercise/quiz-exercise/QuizExerciseParticipation.spec.ts</em></td></tr><tr><td>ts.Quiz Exercise Participation › DnD Quiz participation › Student can participate in DnD Quiz</td><td>❌ failure</td><td>2m 2s 664ms</td></tr></table>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-01T21:56:50Z
body: ### End-to-End (E2E) Test Results Summary

<table><tr><th><th>Tests</th><th>Passed ☑️</th><th>Skipped ⚠️</th><th>Failed ❌️</th><th>Time ⏱</th></tr><tr><td>End-to-End (E2E) Test Report</td><td>201 ran</td><td>194 passed</td><td>3 skipped</td><td>4 failed</td><td>51m 54s 565ms</td></tr></table> <table><tr><th>Test</th><th>Result</th><th>Time ⏱</th></tr><tr><td colspan="3"><strong>End-to-End (E2E) Test Report</strong></td></tr><tr><td colspan="3"><em>e2e/course/CourseMessages.spec.ts</em></td></tr><tr><td>ts.Course messages › Channel messages › Write/edit/delete message in channel › Student should be able to edit message in channel</td><td>❌ failure</td><td>2m 3s 515ms</td></tr><tr><td colspan="3"><em>e2e/exercise/quiz-exercise/QuizExerciseManagement.spec.ts</em></td></tr><tr><td>ts.Quiz Exercise Management › Quiz Exercise Creation › Creates a Quiz with Drag and Drop</td><td>❌ failure</td><td>2m 2s 34ms</td></tr><tr><td colspan="3"><em>e2e/exercise/quiz-exercise/QuizExerciseParticipation.spec.ts</em></td></tr><tr><td>ts.Quiz Exercise Participation › DnD Quiz participation › Student can participate in DnD Quiz</td><td>❌ failure</td><td>2m 2s 561ms</td></tr><tr><td colspan="3"><em>e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts</em></td></tr><tr><td>ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors</td><td>❌ failure</td><td>1m 43s 803ms</td></tr></table>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-02T20:02:35Z
body: ### End-to-End (E2E) Test Results Summary

<table><tr><th><th>Tests</th><th>Passed ☑️</th><th>Skipped ⚠️</th><th>Failed ❌️</th><th>Time ⏱</th></tr><tr><td>End-to-End (E2E) Test Report</td><td>201 ran</td><td>194 passed</td><td>3 skipped</td><td>4 failed</td><td>53m 2s 852ms</td></tr></table> <table><tr><th>Test</th><th>Result</th><th>Time ⏱</th></tr><tr><td colspan="3"><strong>End-to-End (E2E) Test Report</strong></td></tr><tr><td colspan="3"><em>e2e/course/CourseMessages.spec.ts</em></td></tr><tr><td>ts.Course messages › Channel messages › Write/edit/delete message in channel › Student should be able to edit message in channel</td><td>❌ failure</td><td>2m 5s 354ms</td></tr><tr><td colspan="3"><em>e2e/exercise/quiz-exercise/QuizExerciseManagement.spec.ts</em></td></tr><tr><td>ts.Quiz Exercise Management › Quiz Exercise Creation › Creates a Quiz with Drag and Drop</td><td>❌ failure</td><td>2m 2s 358ms</td></tr><tr><td colspan="3"><em>e2e/exercise/quiz-exercise/QuizExerciseParticipation.spec.ts</em></td></tr><tr><td>ts.Quiz Exercise Participation › DnD Quiz participation › Student can participate in DnD Quiz</td><td>❌ failure</td><td>2m 2s 904ms</td></tr><tr><td colspan="3"><em>e2e/exercise/programming/ProgrammingExerciseStaticCodeAnalysis.spec.ts</em></td></tr><tr><td>ts.Static code analysis tests › Configures SCA grading and makes a successful submission with SCA errors</td><td>❌ failure</td><td>1m 44s 930ms</td></tr></table>
author_association: NONE