Skip to content

Conversation

@vargaat
Copy link
Collaborator

@vargaat vargaat commented Feb 4, 2026

What's new?

  • Added course invitations widget for the Student app's Learner Dashboard.
  • Added a reusable HorizontalCarouselView component with page indicator support.
  • Extended Course and Enrollment object save logic to properly link them together. On the old dashboard invitations are fetched directly from API and are not cached in CoreData, now they do. This change affects course and enrollment savings and affects all apps, I poked around and didn't notice any issues but let's keep an eye on this.
  • Added backgroundReadContext to NSPersistentContainer for thread-safe background CoreData operations.
  • Removed example FullWidthWidget widget.
  • Added AcceptCourseInvitation and DeclineCourseInvitation use cases for handling invitation responses and properly updating CoreData cache.
  • Introduced CoursesInteractor that's purpose is to coordinate all course related data flow via this single interactor. The speciality is, that multiple widgets are able to request courses simultaneously, while only sending out one request towards the API and performing one CoreData write. This is to prevent managed object access races when one widget removes and another one reads the same CoreData entity.
  • Successful accept/decline actions are confirmed with snack bars, errors are handled with alerts.
  • Offline mode handled with the usual disabled state and alert.

Known Issues

  • When resizing the window on iPads the carousel doesn't snap to the nearest page but stays mid way scrolled.
  • The invitation card is 8 points narrower than the container. This is to achieve paging by adding horizontal paddings to all cards instead of having a spacer on the horizontal stack. If we have separate spacers then after swiping to the second and beyond cards will render cards off-centered.
  • Full width widgets and grid widgets don't animate as a whole. For example if a course invitation appears, then grid widgets don't nicely animate down but jump to their position. I played around with this a lot but it only made things worse so I settled with this solution.

refs: MBL-19532
builds: Student
affects: Student, Teacher, Parent
release note: none
test plan:

  • Test the usual features you test on a new UI component.

Screenshots

Checklist

  • A11y checked
  • Tested on phone
  • Tested on tablet
  • Tested in dark mode
  • Tested in light mode

vargaat and others added 30 commits January 7, 2026 14:51
refs: MBL-19528
builds: Student
affects: Student
release note: none

test plan:
…ions

# Conflicts:
#	Canvas.xcworkspace/xcshareddata/swiftpm/Package.resolved
- Fix carousel layout issues.
- Centralize course logic to shared interactor.
- Remove no longer needed business logic entities.
…ature/MBL-19532-Course-Invitations

# Conflicts:
#	Student/Student/LearnerDashboard/Widgets/Common/View/DashboardWidgetLayout.swift
#	Student/Student/LearnerDashboard/Widgets/Common/View/LearnerDashboardWidgetLayoutHelpers.swift
#	Student/Student/LearnerDashboard/Widgets/DashboardWidgetIdentifier.swift
#	Student/Student/LearnerDashboard/Widgets/FullWidthWidget/ViewModel/FullWidthWidgetViewModel.swift
#	Student/Student/LearnerDashboard/Widgets/LearnerDashboardWidgetAssembly.swift
- Fix default widgets not getting ordered properly.
- Extracted default widget animation to standalone file.
- Removed unnecessary nav title.
- Remove unnecessary selfs.
…ature/MBL-19532-Course-Invitations

# Conflicts:
#	Student/Student/LearnerDashboard/Widgets/FullWidthWidget/ViewModel/FullWidthWidgetViewModel.swift
@vargaat vargaat self-assigned this Feb 4, 2026
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This PR implements a comprehensive Course Invitations Widget for the Learner Dashboard. The implementation is well-structured with good separation of concerns, comprehensive test coverage, and follows MVVM architecture. However, there are several critical issues that should be addressed before merging.

🎯 What This PR Does

  • Adds Core layer APIs for accepting/declining course invitations
  • Implements a new dashboard widget with horizontal carousel UI
  • Adds request coalescing for optimized network performance
  • Includes database schema changes to track enrollment creation dates
  • Provides comprehensive unit test coverage

✅ Strengths

  • Clean Architecture: Proper MVVM separation with clear boundaries
  • Comprehensive Testing: Good test coverage for ViewModels and Interactors
  • Performance Optimization: Request coalescing prevents duplicate network calls
  • Reusable Components: HorizontalCarouselView can be reused for other widgets
  • Error Handling: Most failure scenarios are handled with user feedback

⚠️ Issues Found

I've identified several issues that need attention. Please review the inline comments for specific code locations and recommendations.

Critical Issues (Must Fix)

  • Memory leak risk in AcceptCourseInvitation.swift - subscriptions never cleared (line 103)
  • Silent error swallowing in AcceptCourseInvitation.swift - failed fetches are not logged (lines 52-75)
  • Potential data loss in GetAllUserCourses.swift - deletes courses on partial API failures (lines 88-92)
  • Race condition in Course.swift - enrollment delete/fetch not synchronized (lines 137-150)
  • Thread safety concern in CoursesInteractor.swift - subscriptions storage not protected (lines 77-84)

High Priority Issues (Should Fix)

  • Force unwrapping in Enrollment.swift - could crash if fetch fails (lines 208-209)
  • Edge case in CourseInvitationCardView.swift - empty section names display as ", " (line 149)
  • Concurrency edge case in CourseInvitationCardViewModel.swift - rapid accept/decline taps (line 62)

Additional Recommendations

  • Consider adding UI/integration tests for the complete invitation flow
  • Add accessibility tests for dynamic content announcements
  • Add timeout configuration for API requests to prevent indefinite loading states
  • Consider extracting hardcoded spacing values to design system constants
  • Add tests for CoreData migration with the new createdAt field

🔍 Testing Notes

The unit tests are comprehensive and cover most success/failure scenarios. Consider adding:

  • UI tests for the actual widget interaction flow
  • Performance tests for request coalescing under high load
  • Edge case tests for horizontal carousel pagination
  • Accessibility tests for VoiceOver announcements

📝 Architecture Notes

The implementation follows the project's MVVM pattern well. Minor observation: some business logic in CourseInvitationsWidgetViewModel.refresh() could potentially be extracted to the Interactor layer for better separation, but this is not a blocking issue.

Overall, this is a well-crafted feature implementation. Once the critical issues are addressed, particularly around thread safety and error handling, this will be ready to merge.

@inst-danger
Copy link
Contributor

inst-danger commented Feb 4, 2026

Warnings
⚠️ This pull request will not generate a release note.

Affected Apps: Student, Teacher, Parent

Builds: Student

MBL-19532

Coverage New % Master % Delta
Canvas iOS 91.49% 81.06% 10.42%

Generated by 🚫 dangerJS against 09bb788

@inst-danger
Copy link
Contributor

inst-danger commented Feb 4, 2026

Builds

Commit: Remove extra new line. (09bb788)
Build Number: 1267
Built At: Feb 06 17:11 CET (02/06 09:11 AM MST)

Student

# Conflicts:
#	Student/Student/LearnerDashboard/Widgets/Common/View/DashboardWidgetLayout.swift
@vargaat vargaat marked this pull request as ready for review February 4, 2026 16:34
@petkybenedek
Copy link
Contributor

Code +1

suhaibabsi-inst
suhaibabsi-inst previously approved these changes Feb 5, 2026
Copy link
Contributor

@suhaibabsi-inst suhaibabsi-inst left a comment

Choose a reason for hiding this comment

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

QA +1

Tested on iPhone 16, iOS 26.2.

import CoreData
import Foundation

public class DeclineCourseInvitation: UseCase {
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting way of using UseCase!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just realized I could have used DeleteUseCase to make this simpler. Next time.

# Conflicts:
#	Student/Student/LearnerDashboard/Widgets/LearnerDashboardWidgetAssembly.swift
#	Student/Student/Localizable.xcstrings
#	Student/StudentUnitTests/LearnerDashboard/Container/Model/LearnerDashboardInteractorTests.swift
petkybenedek
petkybenedek previously approved these changes Feb 5, 2026
Copy link
Contributor

@petkybenedek petkybenedek left a comment

Choose a reason for hiding this comment

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

It's hard to see the individual cards in dark mode. Not connected to this pr.

Approved otherwise

Image

@State private var totalPages: Int = 1

var body: some View {
ZStack {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the ZStack?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Without this, the preview doesn't appear. I tried moving the ZStack to the preview but didn't fix it. If you feel you can play with it, I'm also curious what happens there.

Copy link
Contributor

@rh12 rh12 left a comment

Choose a reason for hiding this comment

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

QA + 1 (tested on iPhone @ 26, iPad @ 18)

Thanks for all the changes!

@rh12 rh12 merged commit 21720c8 into master Feb 6, 2026
5 checks passed
@rh12 rh12 deleted the feature/MBL-19532-Course-Invitations branch February 6, 2026 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants