krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

51b2eb0e6296734960f37cebd40fee34a1f261f9

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-05-14T18:18:55Z

feat: add App Intents for Hutch navigation

- add read-only App Intents for core Hutch workflows
- route intents through the existing Hutch navigation/deep-link model
- expose Work Queue, Recent Activity, System Status, pinned resources, projects, failed builds, assigned tickets, saved searches, and search where supported
- keep App Intents non-mutating for the initial implementation
- preserve existing widget, Safari extension, and hutch:// routing behavior

References: https://todo.sr.ht/~ccleberg/hutch/71
 Hutch/App/AppState.swift                    |   6 +
 Hutch/App/DeepLink.swift                    | 210 ++++++++++++++--
 Hutch/App/HutchApp.swift                    |  18 +-
 Hutch/App/HutchIntents.swift                | 377 ++++++++++++++++++++++++----
 Hutch/App/RootView.swift                    | 124 +++++++--
 Hutch/Views/Builds/BuildListView.swift      |   9 +
 Hutch/Views/Builds/BuildListViewModel.swift |   8 +
 Hutch/Views/Home/HomeView.swift             |   4 +-
 Hutch/Views/Lookup/LookupView.swift         |  26 +-
 Hutch/Views/More/MoreView.swift             |   2 +-
 Hutch/Views/Work/WorkView.swift             |  28 ++-
 HutchTests/BuildListViewModelTests.swift    |   6 +
 HutchTests/DeepLinkTests.swift              |   8 +
 HutchTests/HutchIntentsTests.swift          |   9 +
 Shared/NeedsAttentionSnapshot.swift         |  21 +-
 Shared/SystemStatusWidgetSnapshot.swift     |  17 +-
 16 files changed, 747 insertions(+), 126 deletions(-)

diff --git a/Hutch/App/AppState.swift b/Hutch/App/AppState.swift
index a5500fe..479f610 100644
--- a/Hutch/App/AppState.swift
+++ b/Hutch/App/AppState.swift
@@ -84,6 +84,7 @@ final class AppState {
     /// Set by the deep link handler; consumed by RootView to drive navigation.
     var pendingDeepLink: DeepLink?
     var pendingTabNavigation: TabNavigationTarget?
+    var pendingBuildListFilter: BuildListFilter?
     var deepLinkError: String?
 
     // MARK: - Init
@@ -373,6 +374,10 @@ final class AppState {
         selectedTab = .builds
     }
 
+    func open(_ route: HutchRoute) {
+        pendingDeepLink = DeepLink(route: route)
+    }
+
     func presentRepositoryDeepLinkError() {
         deepLinkError = "The repository could not be found or is inaccessible."
     }
@@ -574,6 +579,7 @@ final class AppState {
     private func resetNavigationState() {
         pendingDeepLink = nil
         pendingTabNavigation = nil
+        pendingBuildListFilter = nil
         deepLinkError = nil
         selectedTab = .home
     }
diff --git a/Hutch/App/DeepLink.swift b/Hutch/App/DeepLink.swift
index 574481d..7467fc8 100644
--- a/Hutch/App/DeepLink.swift
+++ b/Hutch/App/DeepLink.swift
@@ -3,45 +3,50 @@ import os
 
 private let deepLinkParserLogger = Logger(subsystem: "net.cleberg.Hutch", category: "DeepLink")
 
-/// Represents a parsed `hutch://` deep link.
-enum DeepLink: Equatable {
+enum HutchWorkQueueScope: String, CaseIterable, Sendable {
+    case all
+    case unread
+    case assigned
+}
+
+enum HutchRoute: Equatable, Sendable {
     case home
-    case work
-    /// hutch://git/<owner>/<repo> or hutch://hg/<owner>/<repo>
+    case workQueue(scope: HutchWorkQueueScope = .all)
+    case recentActivity
     case repository(service: SRHTService, owner: String, repo: String)
-    /// hutch://todo/<owner>/<tracker>/<ticketId>
+    case tracker(owner: String, tracker: String)
     case ticket(owner: String, tracker: String, ticketId: Int)
-    /// hutch://builds/<jobId> or hutch://builds/<owner>/job/<jobId>
     case build(jobId: Int)
-    /// hutch://lists/<owner>/<list>
     case mailingList(owner: String, list: String)
-    /// hutch://lookup/<owner>
     case userProfile(owner: String)
-    /// hutch://builds (tab-level)
-    case buildsTab
-    /// hutch://repositories (tab-level)
-    case repositoriesTab
-    /// hutch://trackers (tab-level)
-    case trackersTab
-    /// hutch://status
+    case builds
+    case failedBuilds
+    case repositories
+    case trackers
     case systemStatus
-    /// hutch://lookup
     case lookup
+    case search(query: String)
+    case projectDashboard(id: String, title: String?)
 
-    /// Attempt to parse a URL into a DeepLink.
-    /// Expected format: hutch://<path>
     init?(url: URL) {
         guard url.scheme == "hutch" else { return nil }
 
         let components = url.deepLinkPathComponents
-        deepLinkParserLogger.info("DeepLink parser components for \(url.absoluteString, privacy: .public): \(components.joined(separator: ","), privacy: .public)")
+        let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems ?? []
+        let queryValue: (String) -> String? = { name in
+            queryItems.first { $0.name == name }?.value?.trimmingCharacters(in: .whitespacesAndNewlines)
+        }
 
         switch components.first {
         case "home", nil:
             self = .home
 
+        case "recent", "recent-activity", "activity":
+            self = .recentActivity
+
         case "work", "inbox":
-            self = .work
+            let scope = queryValue("scope").flatMap(HutchWorkQueueScope.init(rawValue:)) ?? .all
+            self = .workQueue(scope: scope)
 
         case let .some(serviceName) where ["git", "hg", "todo", "builds", "lists"].contains(serviceName)
             && components.count == 2
@@ -62,6 +67,9 @@ enum DeepLink: Equatable {
             guard let ticketId = Int(components[3]) else { return nil }
             self = .ticket(owner: owner, tracker: tracker, ticketId: ticketId)
 
+        case "todo" where components.count >= 3:
+            self = .tracker(owner: components[1], tracker: components[2])
+
         case "builds" where components.count >= 2:
             let rawJobId: String
             if components.count >= 4, components[2] == "job" {
@@ -73,16 +81,19 @@ enum DeepLink: Equatable {
             self = .build(jobId: jobId)
 
         case "builds":
-            self = .buildsTab
+            self = queryValue("filter") == "failed" ? .failedBuilds : .builds
 
         case "lists" where components.count >= 3:
             self = .mailingList(owner: components[1], list: components[2])
 
+        case "projects" where components.count >= 2:
+            self = .projectDashboard(id: components[1], title: queryValue("title"))
+
         case "repositories":
-            self = .repositoriesTab
+            self = .repositories
 
         case "trackers":
-            self = .trackersTab
+            self = .trackers
 
         case "status":
             self = .systemStatus
@@ -91,12 +102,163 @@ enum DeepLink: Equatable {
             self = .userProfile(owner: components[1])
 
         case "lookup":
-            self = .lookup
+            if let query = queryValue("q"), !query.isEmpty {
+                self = .search(query: query)
+            } else {
+                self = .lookup
+            }
 
         default:
             return nil
         }
     }
+
+    var url: URL {
+        switch self {
+        case .home:
+            return Self.makeURL(host: "home")
+        case .workQueue(let scope):
+            return Self.makeURL(
+                host: "work",
+                queryItems: scope == .all ? [] : [URLQueryItem(name: "scope", value: scope.rawValue)]
+            )
+        case .recentActivity:
+            return Self.makeURL(host: "recent-activity")
+        case .repository(let service, let owner, let repo):
+            return Self.makeURL(host: service.rawValue, path: [owner, repo])
+        case .tracker(let owner, let tracker):
+            return Self.makeURL(host: "todo", path: [owner, tracker])
+        case .ticket(let owner, let tracker, let ticketId):
+            return Self.makeURL(host: "todo", path: [owner, tracker, String(ticketId)])
+        case .build(let jobId):
+            return Self.makeURL(host: "builds", path: [String(jobId)])
+        case .mailingList(let owner, let list):
+            return Self.makeURL(host: "lists", path: [owner, list])
+        case .userProfile(let owner):
+            return Self.makeURL(host: "lookup", path: [owner])
+        case .builds:
+            return Self.makeURL(host: "builds")
+        case .failedBuilds:
+            return Self.makeURL(host: "builds", queryItems: [URLQueryItem(name: "filter", value: "failed")])
+        case .repositories:
+            return Self.makeURL(host: "repositories")
+        case .trackers:
+            return Self.makeURL(host: "trackers")
+        case .systemStatus:
+            return Self.makeURL(host: "status")
+        case .lookup:
+            return Self.makeURL(host: "lookup")
+        case .search(let query):
+            return Self.makeURL(host: "lookup", queryItems: [URLQueryItem(name: "q", value: query)])
+        case .projectDashboard(let id, let title):
+            return Self.makeURL(
+                host: "projects",
+                path: [id],
+                queryItems: title.map { [URLQueryItem(name: "title", value: $0)] } ?? []
+            )
+        }
+    }
+
+    private static func makeURL(
+        host: String,
+        path: [String] = [],
+        queryItems: [URLQueryItem] = []
+    ) -> URL {
+        var components = URLComponents()
+        components.scheme = "hutch"
+        components.host = host
+        if !path.isEmpty {
+            components.path = "/" + path.joined(separator: "/")
+        }
+        if !queryItems.isEmpty {
+            components.queryItems = queryItems
+        }
+        return components.url!
+    }
+}
+
+/// Represents a parsed `hutch://` deep link.
+enum DeepLink: Equatable {
+    case home
+    case work
+    case workQueue(scope: HutchWorkQueueScope)
+    case recentActivity
+    /// hutch://git/<owner>/<repo> or hutch://hg/<owner>/<repo>
+    case repository(service: SRHTService, owner: String, repo: String)
+    /// hutch://todo/<owner>/<tracker>
+    case tracker(owner: String, tracker: String)
+    /// hutch://todo/<owner>/<tracker>/<ticketId>
+    case ticket(owner: String, tracker: String, ticketId: Int)
+    /// hutch://builds/<jobId> or hutch://builds/<owner>/job/<jobId>
+    case build(jobId: Int)
+    /// hutch://lists/<owner>/<list>
+    case mailingList(owner: String, list: String)
+    /// hutch://lookup/<owner>
+    case userProfile(owner: String)
+    /// hutch://builds (tab-level)
+    case buildsTab
+    /// hutch://repositories (tab-level)
+    case repositoriesTab
+    /// hutch://trackers (tab-level)
+    case trackersTab
+    /// hutch://status
+    case systemStatus
+    /// hutch://lookup
+    case lookup
+    /// hutch://lookup?q=<query>
+    case search(query: String)
+    /// hutch://builds?filter=failed
+    case failedBuilds
+    /// hutch://projects/<rid>
+    case projectDashboard(id: String, title: String?)
+
+    /// Attempt to parse a URL into a DeepLink.
+    /// Expected format: hutch://<path>
+    init?(url: URL) {
+        let components = url.deepLinkPathComponents
+        deepLinkParserLogger.info("DeepLink parser components for \(url.absoluteString, privacy: .public): \(components.joined(separator: ","), privacy: .public)")
+        guard let route = HutchRoute(url: url) else { return nil }
+        self = Self(route: route)
+    }
+
+    init(route: HutchRoute) {
+        switch route {
+        case .home:
+            self = .home
+        case .workQueue(let scope):
+            self = scope == .all ? .work : .workQueue(scope: scope)
+        case .recentActivity:
+            self = .recentActivity
+        case .repository(let service, let owner, let repo):
+            self = .repository(service: service, owner: owner, repo: repo)
+        case .tracker(let owner, let tracker):
+            self = .tracker(owner: owner, tracker: tracker)
+        case .ticket(let owner, let tracker, let ticketId):
+            self = .ticket(owner: owner, tracker: tracker, ticketId: ticketId)
+        case .build(let jobId):
+            self = .build(jobId: jobId)
+        case .mailingList(let owner, let list):
+            self = .mailingList(owner: owner, list: list)
+        case .userProfile(let owner):
+            self = .userProfile(owner: owner)
+        case .builds:
+            self = .buildsTab
+        case .failedBuilds:
+            self = .failedBuilds
+        case .repositories:
+            self = .repositoriesTab
+        case .trackers:
+            self = .trackersTab
+        case .systemStatus:
+            self = .systemStatus
+        case .lookup:
+            self = .lookup
+        case .search(let query):
+            self = .search(query: query)
+        case .projectDashboard(let id, let title):
+            self = .projectDashboard(id: id, title: title)
+        }
+    }
 }
 
 private extension URL {
diff --git a/Hutch/App/HutchApp.swift b/Hutch/App/HutchApp.swift
index 69d1311..2b1977a 100644
--- a/Hutch/App/HutchApp.swift
+++ b/Hutch/App/HutchApp.swift
@@ -28,20 +28,10 @@ struct HutchApp: App {
                         deepLinkLogger.error("Rejected URL: \(url.absoluteString, privacy: .public)")
                     }
                 }
-                .onChange(of: HutchIntentNavigator.shared.pendingDestination) { _, destination in
-                    guard let destination else { return }
-                    HutchIntentNavigator.shared.pendingDestination = nil
-                    let link: DeepLink
-                    switch destination {
-                    case .home: link = .home
-                    case .work: link = .work
-                    case .builds: link = .buildsTab
-                    case .repositories: link = .repositoriesTab
-                    case .trackers: link = .trackersTab
-                    case .systemStatus: link = .systemStatus
-                    case .lookup: link = .lookup
-                    }
-                    appState.pendingDeepLink = link
+                .onChange(of: HutchIntentNavigator.shared.pendingRoute) { _, route in
+                    guard let route else { return }
+                    HutchIntentNavigator.shared.pendingRoute = nil
+                    appState.open(route)
                 }
         }
     }
diff --git a/Hutch/App/HutchIntents.swift b/Hutch/App/HutchIntents.swift
index 5751ef5..c2f144a 100644
--- a/Hutch/App/HutchIntents.swift
+++ b/Hutch/App/HutchIntents.swift
@@ -1,46 +1,271 @@
 import AppIntents
 import Foundation
 
-// MARK: - Open Hutch Intent
-
-enum HutchDestination: String, AppEnum {
-    case home
-    case work
-    case builds
-    case repositories
-    case trackers
-    case systemStatus
-    case lookup
-
-    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Hutch Section")
-
-    static var caseDisplayRepresentations: [HutchDestination: DisplayRepresentation] = [
-        .home: "Home",
-        .work: "Work",
-        .builds: "Builds",
-        .repositories: "Repositories",
-        .trackers: "Trackers",
-        .systemStatus: "System Status",
-        .lookup: "Look Up"
+// MARK: - Navigation Intents
+
+struct OpenWorkQueueIntent: AppIntent {
+    static var title: LocalizedStringResource = "Open Work Queue"
+    static var description = IntentDescription("Opens Hutch to your Work Queue.")
+    static var openAppWhenRun = true
+
+    var route: HutchRoute { .workQueue(scope: .all) }
+
+    @MainActor
+    func perform() async throws -> some IntentResult {
+        HutchIntentNavigator.shared.open(route)
+        return .result()
+    }
+}
+
+struct OpenRecentActivityIntent: AppIntent {
+    static var title: LocalizedStringResource = "Open Recent Activity"
+    static var description = IntentDescription("Opens Hutch to recent activity.")
+    static var openAppWhenRun = true
+
+    var route: HutchRoute { .recentActivity }
+
+    @MainActor
+    func perform() async throws -> some IntentResult {
+        HutchIntentNavigator.shared.open(route)
+        return .result()
+    }
+}
+
+struct OpenSystemStatusIntent: AppIntent {
+    static var title: LocalizedStringResource = "Open System Status"
+    static var description = IntentDescription("Opens Hutch to SourceHut system status.")
+    static var openAppWhenRun = true
+
+    var route: HutchRoute { .systemStatus }
+
+    @MainActor
+    func perform() async throws -> some IntentResult {
+        HutchIntentNavigator.shared.open(route)
+        return .result()
+    }
+}
+
+struct OpenPinnedResourceIntent: AppIntent {
+    static var title: LocalizedStringResource = "Open Pinned Resource"
+    static var description = IntentDescription("Opens a pinned Hutch resource.")
+    static var openAppWhenRun = true
+
+    @Parameter(title: "Pinned Resource")
+    var pinnedResource: PinnedResourceEntity
+
+    var route: HutchRoute { pinnedResource.route }
+
+    @MainActor
+    func perform() async throws -> some IntentResult {
+        HutchIntentNavigator.shared.open(route)
+        return .result()
+    }
+}
+
+struct OpenProjectDashboardIntent: AppIntent {
+    static var title: LocalizedStringResource = "Open Project Dashboard"
+    static var description = IntentDescription("Opens a pinned project dashboard in Hutch.")
+    static var openAppWhenRun = true
+
+    @Parameter(title: "Project")
+    var project: ProjectEntity
+
+    var route: HutchRoute {
+        .projectDashboard(id: project.id, title: project.name)
+    }
+
+    @MainActor
+    func perform() async throws -> some IntentResult {
+        HutchIntentNavigator.shared.open(route)
+        return .result()
+    }
+}
+
+enum HutchShortcutScope: String, AppEnum {
+    case all
+
+    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Scope")
+    static var caseDisplayRepresentations: [HutchShortcutScope: DisplayRepresentation] = [
+        .all: "All"
     ]
 }
 
-struct OpenHutchIntent: AppIntent {
-    static var title: LocalizedStringResource = "Open Hutch"
-    static var description = IntentDescription("Opens Hutch to a specific section.")
+struct OpenFailedBuildsIntent: AppIntent {
+    static var title: LocalizedStringResource = "Open Failed Builds"
+    static var description = IntentDescription("Opens Hutch to failed builds.")
     static var openAppWhenRun = true
 
-    @Parameter(title: "Section", default: .home)
-    var destination: HutchDestination
+    @Parameter(title: "Scope", default: .all)
+    var scope: HutchShortcutScope
+
+    var route: HutchRoute { .failedBuilds }
 
     @MainActor
     func perform() async throws -> some IntentResult {
-        HutchIntentNavigator.shared.pendingDestination = destination
+        HutchIntentNavigator.shared.open(route)
         return .result()
     }
 }
 
-// MARK: - Check System Status Intent
+struct OpenAssignedTicketsIntent: AppIntent {
+    static var title: LocalizedStringResource = "Open Assigned Tickets"
+    static var description = IntentDescription("Opens Hutch to tickets assigned to you.")
+    static var openAppWhenRun = true
+
+    @Parameter(title: "Scope", default: .all)
+    var scope: HutchShortcutScope
+
+    var route: HutchRoute { .workQueue(scope: .assigned) }
+
+    @MainActor
+    func perform() async throws -> some IntentResult {
+        HutchIntentNavigator.shared.open(route)
+        return .result()
+    }
+}
+
+struct SearchHutchIntent: AppIntent {
+    static var title: LocalizedStringResource = "Search Hutch"
+    static var description = IntentDescription("Opens Hutch lookup with a search query.")
+    static var openAppWhenRun = true
+
+    @Parameter(title: "Query")
+    var query: String
+
+    var route: HutchRoute {
+        let normalized = query.trimmingCharacters(in: .whitespacesAndNewlines)
+        // TODO: Route to global local search once Hutch has one.
+        return normalized.isEmpty ? .lookup : .search(query: normalized)
+    }
+
+    @MainActor
+    func perform() async throws -> some IntentResult {
+        HutchIntentNavigator.shared.open(route)
+        return .result()
+    }
+}
+
+// TODO: Add OpenSavedSearchIntent when Hutch has global saved-search persistence.
+
+// MARK: - App Entities
+
+struct PinnedResourceEntity: AppEntity, Identifiable {
+    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Pinned Resource")
+    static var defaultQuery = PinnedResourceQuery()
+
+    let id: String
+    let name: String
+    let subtitle: String
+    let route: HutchRoute
+
+    var displayRepresentation: DisplayRepresentation {
+        DisplayRepresentation(title: "\(name)", subtitle: "\(subtitle)")
+    }
+}
+
+struct PinnedResourceQuery: EntityQuery {
+    @MainActor
+    func entities(for identifiers: [PinnedResourceEntity.ID]) async throws -> [PinnedResourceEntity] {
+        HutchIntentEntityStore.pinnedResources().filter { identifiers.contains($0.id) }
+    }
+
+    @MainActor
+    func suggestedEntities() async throws -> [PinnedResourceEntity] {
+        HutchIntentEntityStore.pinnedResources()
+    }
+}
+
+struct ProjectEntity: AppEntity, Identifiable {
+    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Project")
+    static var defaultQuery = ProjectEntityQuery()
+
+    let id: String
+    let name: String
+
+    var displayRepresentation: DisplayRepresentation {
+        DisplayRepresentation(title: "\(name)")
+    }
+}
+
+struct ProjectEntityQuery: EntityQuery {
+    @MainActor
+    func entities(for identifiers: [ProjectEntity.ID]) async throws -> [ProjectEntity] {
+        HutchIntentEntityStore.projects().filter { identifiers.contains($0.id) }
+    }
+
+    @MainActor
+    func suggestedEntities() async throws -> [ProjectEntity] {
+        HutchIntentEntityStore.projects()
+    }
+}
+
+private enum HutchIntentEntityStore {
+    static func pinnedResources() -> [PinnedResourceEntity] {
+        pins().compactMap { makePinnedResource(from: $0) }
+    }
+
+    static func projects() -> [ProjectEntity] {
+        pins().compactMap { pin in
+            guard pin.kind == .project else { return nil }
+            return ProjectEntity(id: pin.value, name: pin.title)
+        }
+    }
+
+    private static func pins() -> [HomePinRecord] {
+        guard let userKey = ContributionWidgetContextStore.loadActor(),
+              !userKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+        else {
+            return []
+        }
+
+        return HomePinStore.loadPins(for: userKey, defaults: activeAccountDefaults)
+    }
+
+    private static var activeAccountDefaults: UserDefaults {
+        let activeID = UserDefaults.standard.string(forKey: AppStorageKeys.activeAccountID) ?? ""
+        guard !activeID.isEmpty else { return .standard }
+        return AccountDefaultsStore.userDefaults(for: activeID)
+    }
+
+    private static func makePinnedResource(from pin: HomePinRecord) -> PinnedResourceEntity? {
+        guard let route = route(for: pin) else { return nil }
+        return PinnedResourceEntity(
+            id: pin.id,
+            name: pin.title,
+            subtitle: pin.subtitle,
+            route: route
+        )
+    }
+
+    private static func route(for pin: HomePinRecord) -> HutchRoute? {
+        switch pin.kind {
+        case .project:
+            return .projectDashboard(id: pin.value, title: pin.title)
+        case .repository:
+            guard let owner = pin.ownerUsername else { return nil }
+            return .repository(
+                service: pin.service ?? .git,
+                owner: formattedOwner(owner),
+                repo: pin.value
+            )
+        case .tracker:
+            guard let owner = pin.ownerUsername else { return nil }
+            return .tracker(owner: formattedOwner(owner), tracker: pin.value)
+        case .mailingList:
+            guard let owner = pin.ownerUsername else { return nil }
+            return .mailingList(owner: formattedOwner(owner), list: pin.value)
+        case .user:
+            guard let owner = pin.ownerUsername else { return nil }
+            return .userProfile(owner: formattedOwner(owner))
+        }
+    }
+
+    private static func formattedOwner(_ owner: String) -> String {
+        owner.hasPrefix("~") ? owner : "~\(owner)"
+    }
+}
+
+// MARK: - Existing Read-Only Summary Intents
 
 struct CheckSystemStatusIntent: AppIntent {
     static var title: LocalizedStringResource = "Check SourceHut Status"
@@ -64,8 +289,6 @@ struct CheckSystemStatusIntent: AppIntent {
     }
 }
 
-// MARK: - Check Builds Intent
-
 struct CheckBuildsIntent: AppIntent {
     static var title: LocalizedStringResource = "Check Hutch Builds"
     static var description = IntentDescription("Returns a summary of your recent build status.")
@@ -107,25 +330,92 @@ struct CheckBuildsIntent: AppIntent {
 struct HutchShortcuts: AppShortcutsProvider {
     static var appShortcuts: [AppShortcut] {
         AppShortcut(
-            intent: OpenHutchIntent(),
+            intent: OpenWorkQueueIntent(),
+            phrases: [
+                "Open my work queue in \(.applicationName)",
+                "Show work in \(.applicationName)"
+            ],
+            shortTitle: "Work Queue",
+            systemImageName: "tray.full"
+        )
+
+        AppShortcut(
+            intent: OpenRecentActivityIntent(),
+            phrases: [
+                "Open recent activity in \(.applicationName)",
+                "Show activity in \(.applicationName)"
+            ],
+            shortTitle: "Recent Activity",
+            systemImageName: "clock.arrow.circlepath"
+        )
+
+        AppShortcut(
+            intent: OpenSystemStatusIntent(),
+            phrases: [
+                "Open system status in \(.applicationName)",
+                "Show SourceHut status in \(.applicationName)"
+            ],
+            shortTitle: "System Status",
+            systemImageName: "server.rack"
+        )
+
+        AppShortcut(
+            intent: OpenPinnedResourceIntent(),
             phrases: [
-                "Open \(.applicationName)",
-                "Open \(.applicationName) \(\.$destination)",
-                "Show my \(.applicationName) \(\.$destination)",
-                "Go to \(\.$destination) in \(.applicationName)"
+                "Open \(\.$pinnedResource) in \(.applicationName)",
+                "Show my pinned \(\.$pinnedResource) in \(.applicationName)"
             ],
-            shortTitle: "Open Hutch",
-            systemImageName: "house"
+            shortTitle: "Pinned Resource",
+            systemImageName: "pin"
+        )
+
+        AppShortcut(
+            intent: OpenProjectDashboardIntent(),
+            phrases: [
+                "Open \(\.$project) dashboard in \(.applicationName)",
+                "Show project \(\.$project) in \(.applicationName)"
+            ],
+            shortTitle: "Project Dashboard",
+            systemImageName: "square.stack.3d.up"
+        )
+
+        AppShortcut(
+            intent: OpenFailedBuildsIntent(),
+            phrases: [
+                "Open failed builds in \(.applicationName)",
+                "Show failed builds in \(.applicationName)"
+            ],
+            shortTitle: "Failed Builds",
+            systemImageName: "exclamationmark.triangle"
+        )
+
+        AppShortcut(
+            intent: OpenAssignedTicketsIntent(),
+            phrases: [
+                "Open assigned tickets in \(.applicationName)",
+                "Show my assigned tickets in \(.applicationName)"
+            ],
+            shortTitle: "Assigned Tickets",
+            systemImageName: "person.crop.circle.badge.checkmark"
+        )
+
+        AppShortcut(
+            intent: SearchHutchIntent(),
+            phrases: [
+                "Search \(.applicationName)",
+                "Look up something in \(.applicationName)"
+            ],
+            shortTitle: "Search Hutch",
+            systemImageName: "magnifyingglass"
         )
 
         AppShortcut(
             intent: CheckSystemStatusIntent(),
             phrases: [
                 "Check \(.applicationName) status",
-                "Is SourceHut up in \(.applicationName)",
-                "SourceHut status in \(.applicationName)"
+                "Is SourceHut up in \(.applicationName)"
             ],
-            shortTitle: "Check SourceHut Status",
+            shortTitle: "Check Status",
             systemImageName: "server.rack"
         )
 
@@ -133,7 +423,6 @@ struct HutchShortcuts: AppShortcutsProvider {
             intent: CheckBuildsIntent(),
             phrases: [
                 "Check my \(.applicationName) builds",
-                "How are my builds in \(.applicationName)",
                 "Build status in \(.applicationName)"
             ],
             shortTitle: "Check Builds",
@@ -148,9 +437,13 @@ struct HutchShortcuts: AppShortcutsProvider {
 @Observable
 final class HutchIntentNavigator {
     static let shared = HutchIntentNavigator()
-    var pendingDestination: HutchDestination?
+    var pendingRoute: HutchRoute?
 
     private init() {
         /* Singleton; external code uses `shared`. */
     }
+
+    func open(_ route: HutchRoute) {
+        pendingRoute = route
+    }
 }
diff --git a/Hutch/App/RootView.swift b/Hutch/App/RootView.swift
index 7c8037e..e223766 100644
--- a/Hutch/App/RootView.swift
+++ b/Hutch/App/RootView.swift
@@ -75,8 +75,8 @@ struct RootView: View {
                 HomeView()
                     .navigationDestination(for: HomeRoute.self) { route in
                         switch route {
-                        case .work:
-                            WorkView()
+                        case .work(let scope):
+                            WorkView(initialScope: scope)
                         }
                     }
             }
@@ -202,9 +202,16 @@ struct RootView: View {
             homePath = NavigationPath()
             appState.selectedTab = .home
 
+        case .recentActivity:
+            homePath = NavigationPath()
+            appState.selectedTab = .home
+
         case .repository(let service, let owner, let repo):
             resolveRepositoryLink(service: service, owner: owner, repo: repo)
 
+        case .tracker(let owner, let tracker):
+            resolveTrackerLink(owner: owner, tracker: tracker)
+
         case .build(let jobId):
             buildsPath = NavigationPath()
             appState.selectedTab = .builds
@@ -223,11 +230,39 @@ struct RootView: View {
             resolveUserProfileLink(owner: owner)
 
         case .work:
-            homePath = NavigationPath()
-            appState.selectedTab = .home
+            navigateToWork(scope: .all)
+
+        case .workQueue(let scope):
+            navigateToWork(scope: scope)
+
+        case .projectDashboard(let id, let title):
+            morePath = NavigationPath()
+            appState.selectedTab = .more
+            Task {
+                await settleNavigationTransition()
+                morePath.append(MoreRoute.projects)
+                morePath.append(MoreRoute.projectDashboard(id: id, title: title))
+            }
+
+        case .failedBuilds:
+            buildsPath = NavigationPath()
+            appState.pendingBuildListFilter = .failed
+            appState.selectedTab = .builds
+
+        case .search(let query):
+            morePath = NavigationPath()
+            appState.selectedTab = .more
             Task {
                 await settleNavigationTransition()
-                homePath.append(HomeRoute.work)
+                morePath.append(MoreRoute.lookup(query: query))
+            }
+
+        case .lookup:
+            morePath = NavigationPath()
+            appState.selectedTab = .more
+            Task {
+                await settleNavigationTransition()
+                morePath.append(MoreRoute.lookup(query: nil))
             }
 
         case .buildsTab:
@@ -244,14 +279,15 @@ struct RootView: View {
 
         case .systemStatus:
             appState.navigateToSystemStatus()
+        }
+    }
 
-        case .lookup:
-            morePath = NavigationPath()
-            appState.selectedTab = .more
-            Task {
-                await settleNavigationTransition()
-                morePath.append(MoreRoute.lookup)
-            }
+    private func navigateToWork(scope: HutchWorkQueueScope) {
+        homePath = NavigationPath()
+        appState.selectedTab = .home
+        Task {
+            await settleNavigationTransition()
+            homePath.append(HomeRoute.work(scope: scope))
         }
     }
 
@@ -310,6 +346,22 @@ struct RootView: View {
         }
     }
 
+    private func resolveTrackerLink(owner: String, tracker: String) {
+        isResolvingDeepLink = true
+        Task {
+            defer { isResolvingDeepLink = false }
+            do {
+                let trackerSummary = try await appState.resolveTracker(owner: owner, name: tracker)
+                ticketsPath = NavigationPath()
+                appState.selectedTab = .tickets
+                await settleNavigationTransition()
+                ticketsPath.append(trackerSummary)
+            } catch {
+                appState.presentTicketDeepLinkError()
+            }
+        }
+    }
+
     private func resolveMailingListLink(owner: String, list: String) {
         isResolvingDeepLink = true
         Task {
@@ -375,7 +427,7 @@ enum MoreDestination: Hashable {
 }
 
 enum MoreRoute: Hashable {
-    case lookup
+    case lookup(query: String?)
     case projects
     case lists
     case pastes
@@ -384,6 +436,7 @@ enum MoreRoute: Hashable {
     case settings
     case about
     case userProfile(String)
+    case projectDashboard(id: String, title: String?)
     case mailingList(InboxMailingListReference)
     case thread(InboxThreadSummary)
     case manPageBrowser
@@ -397,8 +450,8 @@ private struct MoreNavigationRoot: View {
         MoreView()
             .navigationDestination(for: MoreRoute.self) { route in
                 switch route {
-                case .lookup:
-                    LookupView()
+                case .lookup(let query):
+                    LookupView(initialQuery: query ?? "")
                 case .projects:
                     ProjectsListView()
                 case .lists:
@@ -415,6 +468,8 @@ private struct MoreNavigationRoot: View {
                     AboutView()
                 case .userProfile(let owner):
                     UserProfileDeepLinkView(owner: owner)
+                case .projectDashboard(let id, let title):
+                    ProjectDashboardDeepLinkView(projectID: id, title: title)
                 case .mailingList(let mailingList):
                     MailingListDetailView(mailingList: mailingList)
                 case .thread(let thread):
@@ -484,6 +539,45 @@ struct UserProfileDeepLinkView: View {
     }
 }
 
+struct ProjectDashboardDeepLinkView: View {
+    @Environment(AppState.self) private var appState
+    let projectID: String
+    let title: String?
+    @State private var project: Project?
+    @State private var errorMessage: String?
+
+    var body: some View {
+        Group {
+            if let project {
+                ProjectDetailView(project: project)
+            } else if let errorMessage {
+                ContentUnavailableView(
+                    "Couldn't Open Project",
+                    systemImage: "square.stack.3d.up.slash",
+                    description: Text(errorMessage)
+                )
+            } else {
+                SRHTLoadingStateView(message: "Loading project...")
+            }
+        }
+        .navigationTitle(title ?? "Project")
+        .navigationBarTitleDisplayMode(.inline)
+        .task(id: projectID) {
+            await loadProject()
+        }
+    }
+
+    @MainActor
+    private func loadProject() async {
+        errorMessage = nil
+        do {
+            project = try await ProjectService(client: appState.client).fetchProjectDetail(rid: projectID)
+        } catch {
+            errorMessage = "The project could not be found or is inaccessible."
+        }
+    }
+}
+
 // MARK: - Ticket Deep Link Navigation Target
 
 /// Hashable wrapper to push a ticket detail view from a deep link.
diff --git a/Hutch/Views/Builds/BuildListView.swift b/Hutch/Views/Builds/BuildListView.swift
index 20414d6..cc31e67 100644
--- a/Hutch/Views/Builds/BuildListView.swift
+++ b/Hutch/Views/Builds/BuildListView.swift
@@ -124,6 +124,10 @@ struct BuildListView: View {
                 let vm = BuildListViewModel(client: appState.client, defaults: appState.accountDefaults)
                 vm.repoFilter = savedRepoFilter
                 vm.lookbackDays = lookbackDays
+                if let pendingFilter = appState.pendingBuildListFilter {
+                    vm.filter = pendingFilter
+                    appState.pendingBuildListFilter = nil
+                }
                 viewModel = vm
                 await vm.loadJobs()
             }
@@ -134,6 +138,11 @@ struct BuildListView: View {
         .onChange(of: lookbackDays) { _, newValue in
             viewModel?.lookbackDays = newValue
         }
+        .onChange(of: appState.pendingBuildListFilter) { _, newValue in
+            guard let newValue else { return }
+            viewModel?.filter = newValue
+            appState.pendingBuildListFilter = nil
+        }
         .onDisappear {
             viewModel?.stopAutoRefresh()
         }
diff --git a/Hutch/Views/Builds/BuildListViewModel.swift b/Hutch/Views/Builds/BuildListViewModel.swift
index 42500a8..eb01d9e 100644
--- a/Hutch/Views/Builds/BuildListViewModel.swift
+++ b/Hutch/Views/Builds/BuildListViewModel.swift
@@ -21,6 +21,7 @@ private struct SubmittedJob: Decodable, Sendable {
 
 enum BuildListFilter: String, CaseIterable, Sendable {
     case attention = "Attention"
+    case failed = "Failed"
     case active = "Active"
     case all = "All"
 }
@@ -413,6 +414,13 @@ final class BuildListViewModel {
                 case .success, .cancelled:
                     return false
                 }
+            case .failed:
+                switch job.status {
+                case .failed, .timeout:
+                    return true
+                case .success, .cancelled, .running, .queued, .pending:
+                    return false
+                }
             case .active:
                 switch job.status {
                 case .running, .queued, .pending:
diff --git a/Hutch/Views/Home/HomeView.swift b/Hutch/Views/Home/HomeView.swift
index 9e06a00..409d095 100644
--- a/Hutch/Views/Home/HomeView.swift
+++ b/Hutch/Views/Home/HomeView.swift
@@ -106,7 +106,7 @@ struct HomeView: View {
 
     private func workSection(_ viewModel: HomeViewModel) -> some View {
         Section("Work") {
-            NavigationLink(value: HomeRoute.work) {
+            NavigationLink(value: HomeRoute.work(scope: .all)) {
                 HomeSummaryRow(
                     title: workTitle(viewModel),
                     summary: workSummary(viewModel),
@@ -424,7 +424,7 @@ struct HomeView: View {
 }
 
 enum HomeRoute: Hashable {
-    case work
+    case work(scope: HutchWorkQueueScope)
 }
 
 private enum HomeSummaryEmphasis {
diff --git a/Hutch/Views/Lookup/LookupView.swift b/Hutch/Views/Lookup/LookupView.swift
index 55c421b..6f7ebe1 100644
--- a/Hutch/Views/Lookup/LookupView.swift
+++ b/Hutch/Views/Lookup/LookupView.swift
@@ -92,11 +92,17 @@ final class LookupViewModel {
         )
     }
 
-    init(client: SRHTClient, appState: AppState, defaults: UserDefaults = .standard) {
+    init(
+        client: SRHTClient,
+        appState: AppState,
+        defaults: UserDefaults = .standard,
+        initialQuery: String = ""
+    ) {
         self.client = client
         self.appState = appState
         self.defaults = defaults
         self.history = LookupHistoryStore.load(defaults: defaults)
+        self.inputText = initialQuery.trimmingCharacters(in: .whitespacesAndNewlines)
     }
 
     func lookup() async {
@@ -329,6 +335,11 @@ final class LookupViewModel {
 struct LookupView: View {
     @Environment(AppState.self) private var appState
     @State private var viewModel: LookupViewModel?
+    private let initialQuery: String
+
+    init(initialQuery: String = "") {
+        self.initialQuery = initialQuery
+    }
 
     var body: some View {
         Group {
@@ -341,7 +352,12 @@ struct LookupView: View {
         .navigationTitle("Look Up")
         .task {
             if viewModel == nil {
-                viewModel = LookupViewModel(client: appState.client, appState: appState, defaults: appState.accountDefaults)
+                viewModel = LookupViewModel(
+                    client: appState.client,
+                    appState: appState,
+                    defaults: appState.accountDefaults,
+                    initialQuery: initialQuery
+                )
             }
         }
     }
@@ -427,8 +443,8 @@ struct LookupView: View {
             }
             .navigationDestination(for: MoreRoute.self) { route in
                 switch route {
-                case .lookup:
-                    LookupView()
+                case .lookup(let query):
+                    LookupView(initialQuery: query ?? "")
                 case .projects:
                     ProjectsListView()
                 case .lists:
@@ -445,6 +461,8 @@ struct LookupView: View {
                     AboutView()
                 case .userProfile(let owner):
                     UserProfileDeepLinkView(owner: owner)
+                case .projectDashboard(let id, let title):
+                    ProjectDashboardDeepLinkView(projectID: id, title: title)
                 case .mailingList(let mailingList):
                     MailingListDetailView(mailingList: mailingList)
                 case .thread(let thread):
diff --git a/Hutch/Views/More/MoreView.swift b/Hutch/Views/More/MoreView.swift
index 5c757ef..ad02077 100644
--- a/Hutch/Views/More/MoreView.swift
+++ b/Hutch/Views/More/MoreView.swift
@@ -13,7 +13,7 @@ struct MoreView: View {
     var body: some View {
         List {
             Section("Search") {
-                NavigationLink(value: MoreRoute.lookup) {
+                NavigationLink(value: MoreRoute.lookup(query: nil)) {
                     Label("Look Up", systemImage: "magnifyingglass")
                 }
                 .themedRow()
diff --git a/Hutch/Views/Work/WorkView.swift b/Hutch/Views/Work/WorkView.swift
index 8b932dd..6ba1d0c 100644
--- a/Hutch/Views/Work/WorkView.swift
+++ b/Hutch/Views/Work/WorkView.swift
@@ -1,20 +1,28 @@
 import SwiftUI
 
-struct WorkView: View {
-    private enum Scope: String, CaseIterable, Identifiable {
-        case all = "All"
-        case unread = "Unread"
-        case assigned = "Assigned"
-
-        var id: String { rawValue }
+extension HutchWorkQueueScope: Identifiable {
+    var id: String { rawValue }
+
+    var displayName: String {
+        switch self {
+        case .all: "All"
+        case .unread: "Unread"
+        case .assigned: "Assigned"
+        }
     }
+}
 
+struct WorkView: View {
     @AppStorage(AppStorageKeys.swipeActionsEnabled, store: .standard) private var swipeActionsEnabled = true
     @Environment(AppState.self) private var appState
     @Environment(\.isAMOLEDTheme) private var isAMOLED
     @Environment(\.scenePhase) private var scenePhase
     @State private var viewModel: HomeViewModel?
-    @State private var scope: Scope = .all
+    @State private var scope: HutchWorkQueueScope
+
+    init(initialScope: HutchWorkQueueScope = .all) {
+        _scope = State(initialValue: initialScope)
+    }
 
     var body: some View {
         Group {
@@ -106,8 +114,8 @@ struct WorkView: View {
     private var scopeSection: some View {
         Section {
             Picker("Scope", selection: $scope) {
-                ForEach(Scope.allCases) { scope in
-                    Text(scope.rawValue).tag(scope)
+                ForEach(HutchWorkQueueScope.allCases) { scope in
+                    Text(scope.displayName).tag(scope)
                 }
             }
             .pickerStyle(.segmented)
diff --git a/HutchTests/BuildListViewModelTests.swift b/HutchTests/BuildListViewModelTests.swift
index aa2e46b..8ee5b81 100644
--- a/HutchTests/BuildListViewModelTests.swift
+++ b/HutchTests/BuildListViewModelTests.swift
@@ -59,9 +59,15 @@ struct BuildListViewModelTests {
             filter: .active,
             lookbackDays: BuildListViewModel.defaultLookbackDays
         )
+        let failed = BuildListViewModel.filterJobs(
+            jobs,
+            filter: .failed,
+            lookbackDays: BuildListViewModel.defaultLookbackDays
+        )
 
         #expect(attention.map(\.id) == [2, 3])
         #expect(active.map(\.id) == [3])
+        #expect(failed.map(\.id) == [2])
     }
 
     @Test
diff --git a/HutchTests/DeepLinkTests.swift b/HutchTests/DeepLinkTests.swift
index 500c4cd..d62cfef 100644
--- a/HutchTests/DeepLinkTests.swift
+++ b/HutchTests/DeepLinkTests.swift
@@ -88,6 +88,14 @@ struct DeepLinkTests {
         #expect(link == .lookup)
     }
 
+    @Test
+    func parsesRouteBackedNavigationLinks() {
+        #expect(DeepLink(url: HutchRoute.workQueue(scope: .assigned).url) == .workQueue(scope: .assigned))
+        #expect(DeepLink(url: HutchRoute.failedBuilds.url) == .failedBuilds)
+        #expect(DeepLink(url: HutchRoute.search(query: "patch queue").url) == .search(query: "patch queue"))
+        #expect(DeepLink(url: HutchRoute.projectDashboard(id: "project-1", title: "Hutch").url) == .projectDashboard(id: "project-1", title: "Hutch"))
+    }
+
     @Test
     func parsesUserProfileLink() {
         let link = DeepLink(url: HutchDeepLinkURL.userProfile)
diff --git a/HutchTests/HutchIntentsTests.swift b/HutchTests/HutchIntentsTests.swift
index 3cb7ad6..ddbe973 100644
--- a/HutchTests/HutchIntentsTests.swift
+++ b/HutchTests/HutchIntentsTests.swift
@@ -5,6 +5,15 @@ import Testing
 @MainActor
 struct HutchIntentsTests {
 
+    @Test
+    func navigationIntentsMapToCentralRoutes() {
+        #expect(OpenWorkQueueIntent().route == .workQueue(scope: .all))
+        #expect(OpenRecentActivityIntent().route == .recentActivity)
+        #expect(OpenSystemStatusIntent().route == .systemStatus)
+        #expect(OpenFailedBuildsIntent().route == .failedBuilds)
+        #expect(OpenAssignedTicketsIntent().route == .workQueue(scope: .assigned))
+    }
+
     @Test
     func checkSystemStatusReturnsOperationalWhenNoDisruption() async throws {
         let defaultsName = "HutchIntentsTests-status-\(UUID().uuidString)"
diff --git a/Shared/NeedsAttentionSnapshot.swift b/Shared/NeedsAttentionSnapshot.swift
index d4c581c..5f584c3 100644
--- a/Shared/NeedsAttentionSnapshot.swift
+++ b/Shared/NeedsAttentionSnapshot.swift
@@ -58,11 +58,11 @@ enum NeedsAttentionSnapshotStore {
     private static let snapshotKey = "needsAttention.snapshot"
 
     static func load(
-        accountID: String? = ActiveAccountContextStore.load(),
+        accountID: String? = nil,
         defaults: UserDefaults? = sharedDefaults()
     ) -> NeedsAttentionSnapshot? {
         guard let defaults,
-              let data = defaults.data(forKey: scopedKey(for: accountID)) else {
+              let data = defaults.data(forKey: scopedKey(for: resolvedAccountID(accountID, defaults: defaults))) else {
             return nil
         }
 
@@ -71,7 +71,7 @@ enum NeedsAttentionSnapshotStore {
 
     static func save(
         _ snapshot: NeedsAttentionSnapshot,
-        accountID: String? = ActiveAccountContextStore.load(),
+        accountID: String? = nil,
         defaults: UserDefaults? = sharedDefaults()
     ) {
         guard let defaults,
@@ -79,7 +79,7 @@ enum NeedsAttentionSnapshotStore {
             return
         }
 
-        defaults.set(data, forKey: scopedKey(for: accountID))
+        defaults.set(data, forKey: scopedKey(for: resolvedAccountID(accountID, defaults: defaults)))
         reloadWidgetTimelines()
     }
 
@@ -87,7 +87,7 @@ enum NeedsAttentionSnapshotStore {
         unreadInboxThreads: Int? = nil,
         assignedOpenTickets: Int? = nil,
         failedBuilds: Int? = nil,
-        accountID: String? = ActiveAccountContextStore.load(),
+        accountID: String? = nil,
         defaults: UserDefaults? = sharedDefaults()
     ) {
         let existing = load(accountID: accountID, defaults: defaults)
@@ -102,7 +102,7 @@ enum NeedsAttentionSnapshotStore {
 
     static func adjustUnreadInboxThreads(
         by delta: Int,
-        accountID: String? = ActiveAccountContextStore.load(),
+        accountID: String? = nil,
         defaults: UserDefaults? = sharedDefaults()
     ) {
         guard let existing = load(accountID: accountID, defaults: defaults),
@@ -123,10 +123,11 @@ enum NeedsAttentionSnapshotStore {
     }
 
     static func clear(
-        accountID: String? = ActiveAccountContextStore.load(),
+        accountID: String? = nil,
         defaults: UserDefaults? = sharedDefaults()
     ) {
-        defaults?.removeObject(forKey: scopedKey(for: accountID))
+        guard let defaults else { return }
+        defaults.removeObject(forKey: scopedKey(for: resolvedAccountID(accountID, defaults: defaults)))
         reloadWidgetTimelines()
     }
 
@@ -139,6 +140,10 @@ enum NeedsAttentionSnapshotStore {
         return "\(snapshotKey).\(accountID)"
     }
 
+    private static func resolvedAccountID(_ accountID: String?, defaults: UserDefaults) -> String? {
+        accountID ?? ActiveAccountContextStore.load(defaults: defaults)
+    }
+
     private static func reloadWidgetTimelines() {
         #if canImport(WidgetKit)
         WidgetCenter.shared.reloadTimelines(ofKind: NeedsAttentionWidgetConfiguration.kind)
diff --git a/Shared/SystemStatusWidgetSnapshot.swift b/Shared/SystemStatusWidgetSnapshot.swift
index 0b19365..025e58c 100644
--- a/Shared/SystemStatusWidgetSnapshot.swift
+++ b/Shared/SystemStatusWidgetSnapshot.swift
@@ -34,11 +34,11 @@ enum SystemStatusWidgetSnapshotStore {
     private static let snapshotKey = "systemStatus.widgetSnapshot"
 
     static func load(
-        accountID: String? = ActiveAccountContextStore.load(),
+        accountID: String? = nil,
         defaults: UserDefaults? = sharedDefaults()
     ) -> SystemStatusWidgetSnapshot? {
         guard let defaults,
-              let data = defaults.data(forKey: scopedKey(for: accountID)) else {
+              let data = defaults.data(forKey: scopedKey(for: resolvedAccountID(accountID, defaults: defaults))) else {
             return nil
         }
         return try? JSONDecoder().decode(SystemStatusWidgetSnapshot.self, from: data)
@@ -46,22 +46,23 @@ enum SystemStatusWidgetSnapshotStore {
 
     static func save(
         _ snapshot: SystemStatusWidgetSnapshot,
-        accountID: String? = ActiveAccountContextStore.load(),
+        accountID: String? = nil,
         defaults: UserDefaults? = sharedDefaults()
     ) {
         guard let defaults,
               let data = try? JSONEncoder().encode(snapshot) else {
             return
         }
-        defaults.set(data, forKey: scopedKey(for: accountID))
+        defaults.set(data, forKey: scopedKey(for: resolvedAccountID(accountID, defaults: defaults)))
         reloadWidgetTimelines()
     }
 
     static func clear(
-        accountID: String? = ActiveAccountContextStore.load(),
+        accountID: String? = nil,
         defaults: UserDefaults? = sharedDefaults()
     ) {
-        defaults?.removeObject(forKey: scopedKey(for: accountID))
+        guard let defaults else { return }
+        defaults.removeObject(forKey: scopedKey(for: resolvedAccountID(accountID, defaults: defaults)))
         reloadWidgetTimelines()
     }
 
@@ -74,6 +75,10 @@ enum SystemStatusWidgetSnapshotStore {
         return "\(snapshotKey).\(accountID)"
     }
 
+    private static func resolvedAccountID(_ accountID: String?, defaults: UserDefaults) -> String? {
+        accountID ?? ActiveAccountContextStore.load(defaults: defaults)
+    }
+
     private static func reloadWidgetTimelines() {
         #if canImport(WidgetKit)
         WidgetCenter.shared.reloadTimelines(ofKind: SystemStatusWidgetConfiguration.kind)