diff --git a/frontend/src/app/components/echo-header/echo-header.component.scss b/frontend/src/app/components/echo-header/echo-header.component.scss
index c5b10ae..08f56a7 100644
--- a/frontend/src/app/components/echo-header/echo-header.component.scss
+++ b/frontend/src/app/components/echo-header/echo-header.component.scss
@@ -4,6 +4,9 @@
border-bottom: 1px solid var(--ee-color-border-subtle, #dccdb7);
box-shadow: var(--ee-shadow-raised, 0 12px 28px rgba(16, 32, 51, 0.12));
backdrop-filter: none;
+ position: sticky;
+ top: 0;
+ z-index: 55;
}
.echo-header button,
diff --git a/frontend/src/app/pages/home/home.component.scss b/frontend/src/app/pages/home/home.component.scss
index 5936722..82c0434 100644
--- a/frontend/src/app/pages/home/home.component.scss
+++ b/frontend/src/app/pages/home/home.component.scss
@@ -43,10 +43,13 @@
.home-shell__main {
min-width: 0;
+ max-height: 100vh;
+ overflow: hidden;
}
.home-shell__content {
min-width: 0;
+ scroll-padding-top: 1rem;
}
.ee-shell__workspace {
diff --git a/frontend/src/app/pages/learner-portal/learner-portal.component.spec.ts b/frontend/src/app/pages/learner-portal/learner-portal.component.spec.ts
index 6490025..0943ae1 100644
--- a/frontend/src/app/pages/learner-portal/learner-portal.component.spec.ts
+++ b/frontend/src/app/pages/learner-portal/learner-portal.component.spec.ts
@@ -140,6 +140,19 @@ describe('LearnerPortalComponent', () => {
expect(component.activeCourseStatusLabel).toBe('In progress');
});
+ it('fills progress bars to the normalized percentage width', () => {
+ fixture.detectChanges();
+
+ const activeProgressBar = fixture.nativeElement.querySelector('[aria-label="Active course progress"]') as HTMLElement;
+ expect(activeProgressBar.style.width).toBe('42%');
+ expect(activeProgressBar.getAttribute('aria-valuenow')).toBe('42');
+ });
+
+ it('normalizes fractional progress values before rendering width', () => {
+ expect(component.normalizedProgress(0.42)).toBe(42);
+ expect(component.progressWidth(0.42)).toBe('42%');
+ });
+
it('navigates course continuation through the canonical /learn lesson route', () => {
component.startCourse(makeStudentCourse());
diff --git a/frontend/src/app/pages/learner-portal/learner-portal.component.ts b/frontend/src/app/pages/learner-portal/learner-portal.component.ts
index 160b31f..957a268 100644
--- a/frontend/src/app/pages/learner-portal/learner-portal.component.ts
+++ b/frontend/src/app/pages/learner-portal/learner-portal.component.ts
@@ -82,7 +82,7 @@ import { V2PlatformService } from '../../services/v2-platform.service';
0 && numericValue <= 1 ? numericValue * 100 : numericValue;
+ return Math.max(0, Math.min(100, percentValue));
+ }
+
+ progressWidth(value: number | undefined): string {
+ return `${this.normalizedProgress(value)}%`;
}
statusLabel(status: string | undefined, progress: number | undefined): string {
diff --git a/frontend/src/app/pages/learner-portal/student-course-overview.component.spec.ts b/frontend/src/app/pages/learner-portal/student-course-overview.component.spec.ts
index 85c1c82..4230ead 100644
--- a/frontend/src/app/pages/learner-portal/student-course-overview.component.spec.ts
+++ b/frontend/src/app/pages/learner-portal/student-course-overview.component.spec.ts
@@ -110,6 +110,7 @@ describe('StudentCourseOverviewComponent', () => {
it('loads the authorized course overview with unit hierarchy and current lesson state', () => {
const compiled = fixture.nativeElement as HTMLElement;
+ const progressBar = compiled.querySelector('[aria-label="Course progress"]') as HTMLElement;
expect(coursesService.getCourseById).toHaveBeenCalledWith('course-1');
expect(compiled.textContent).toContain('Introduction to Africa');
@@ -119,6 +120,19 @@ describe('StudentCourseOverviewComponent', () => {
expect(compiled.textContent).toContain('Locked');
expect(component.curriculumUnits[0].lessons[0].canOpen).toBeTrue();
expect(component.curriculumUnits[0].lessons[1].canOpen).toBeFalse();
+ expect(progressBar.style.width).toBe('25%');
+ expect(progressBar.getAttribute('aria-valuenow')).toBe('25');
+ });
+
+ it('normalizes fractional course progress values before rendering width', () => {
+ coursesService.progressResponse = of(0.5);
+ fixture = TestBed.createComponent(StudentCourseOverviewComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ const progressBar = fixture.nativeElement.querySelector('[aria-label="Course progress"]') as HTMLElement;
+ expect(component.courseProgress).toBe(50);
+ expect(progressBar.style.width).toBe('50%');
});
it('resumes an in-progress course through the governed start-course endpoint', () => {
diff --git a/frontend/src/app/pages/learner-portal/student-course-overview.component.ts b/frontend/src/app/pages/learner-portal/student-course-overview.component.ts
index 2acd857..c0bcb81 100644
--- a/frontend/src/app/pages/learner-portal/student-course-overview.component.ts
+++ b/frontend/src/app/pages/learner-portal/student-course-overview.component.ts
@@ -86,7 +86,7 @@ import { ToastService } from '../../services/toast.service';
aria-valuemin="0"
aria-valuemax="100"
[attr.aria-valuenow]="courseProgress"
- [style.width.%]="courseProgress"
+ [style.width]="progressWidth(courseProgress)"
>
@@ -598,6 +598,12 @@ export class StudentCourseOverviewComponent implements OnInit {
}
private normalizeProgress(progress: number | undefined): number {
- return Math.max(0, Math.min(100, progress || 0));
+ const numericProgress = progress || 0;
+ const percentProgress = numericProgress > 0 && numericProgress <= 1 ? numericProgress * 100 : numericProgress;
+ return Math.max(0, Math.min(100, percentProgress));
+ }
+
+ progressWidth(progress: number | undefined): string {
+ return `${this.normalizeProgress(progress)}%`;
}
}