A native macOS port of Easel, the Canvas LMS client. This replaces the Tauri (Rust + React) stack with a pure Swift/SwiftUI app while keeping the same authentication model and Canvas API behavior.
The Xcode project is generated from project.yml with
XcodeGen (it is not checked in):
brew install xcodegen # once
cd apple
xcodegen generate # writes Easel.xcodeproj
open Easel.xcodeproj # or: xcodebuild -scheme Easel buildRequirements: macOS 14+, Xcode 16+ (built with Xcode 26 / Swift 6.2).
| Concern | Original (Tauri) | Native port |
|---|---|---|
| SSO login | Rust opens a WebviewWindow at /login, harvests cookies |
LoginWebView (WKWebView) + WKHTTPCookieStore harvest |
| Session storage | macOS Keychain (release) / JSON (debug) | SessionStore → Keychain (Security) always |
| API transport | reqwest cookie jar |
CanvasClient actor over URLSession (ephemeral cookie jar) |
| CSRF | X-CSRF-Token + refresh/retry on 401/403/422 |
identical logic in CanvasClient.request |
| Pagination | Link: rel="next" follow, 50-page cap |
CanvasClient.getAll |
| State | Zustand + TanStack Query | @Observable AuthManager / AppData / Prefs |
| Routing | TanStack Router | NavigationSplitView + NavigationStack |
| Canvas HTML | DOMPurify + dangerouslySetInnerHTML |
CanvasHTMLView (NSAttributedString → AttributedString) |
LoginViewcollects a Canvas domain and presentsLoginWebView.- The web view loads
https://{domain}/login; the user completes SSO (e.g. Duke Shibboleth) in the embedded browser. - On the post-SSO redirect away from
/login, the navigation delegate harvests all cookies (including HttpOnly session cookies) from the web data store, capturing_csrf_tokenand requiring a_normandy_session/canvas_sessioncookie as proof of a completed login. - The resulting
Sessionis saved to the Keychain and seeded into theCanvasClientcookie jar; subsequent API calls are authenticated. - On launch,
AuthManager.bootstrap()reloads any stored session. Logout POSTs/logout, clears the Keychain, and wipesWKWebsiteDataStoreso the IdP session is also cleared.
Easel/
App/ App entry + root auth gate
Auth/ AuthManager, SessionStore (Keychain), LoginWebView
Networking/ CanvasClient (transport), CanvasAPI (typed endpoints)
Models/ Session + Codable Canvas models
Stores/ AppData (course/user cache), Prefs (local UI prefs)
Util/ Formatters, ContextCode helpers
Views/ Login, AppShell, Dashboard, Courses, Calendar, Inbox,
Announcements, and Course/ (tabbed course workspace)