Watch: activity line on working cards, All threads browsing, a thread's subagents, Load earlier, and markdown messages

This commit is contained in:
gib committed 2026-11-02 13:10:02 -05:00
1 parent abd2fd0e13
commit d916bfbab5
21 files changed
+928 -46

No files matched your search

+31 -4
View File
@@ -1,4 +1,5 @@
// App-wide state: whether the watch is paired, the navigation path, and the latest feed. It also
// App-wide state: whether the watch is paired, the navigation path, the latest feed, and the hosts
// and projects All threads browses. It also
// keeps the complication current: it shares the mode and latest counts through the shared Keychain
// group and reloads the complication's timeline when they change.
@@ -37,19 +38,26 @@ final class AppModel {
private(set) var settlingAll = false
/// Why Settle all failed, or how many threads it could not settle. The Recent header shows it.
private(set) var settleAllError: String?
/// Hosts and their projects for All threads, loaded when its screens appear.
private(set) var projects: ProjectsResponse?
private(set) var projectsError: String?
private var apnsToken: String?
private var refreshing = false
/// The counts the complication was last told about.
private var sharedSummary: Summary?
init(mode: Mode, feed: Feed? = nil, allRecent: [FeedItem]? = nil, path: [Route] = []) {
init(
mode: Mode, feed: Feed? = nil, allRecent: [FeedItem]? = nil, projects: ProjectsResponse? = nil,
path: [Route] = []
) {
self.mode = mode
self.feed = feed
self.allRecent = allRecent
self.projects = projects
self.path = path
}
/// `-demo` starts in demo mode. `-screen new|recent|status|notifications`, `-host <hostId>` or
/// `-demo` starts in demo mode. `-screen new|recent|status|notifications|threads`, `-host <hostId>` or
/// `-thread <hostId>/<threadId>` opens that screen, which is how the screenshots in
/// watch/screenshots are taken.
convenience init(launchArguments args: [String]) {
@@ -66,6 +74,7 @@ final class AppModel {
case "recent": path = [.recent]
case "status": path = [.status]
case "notifications": path = [.status, .notifications]
case "threads": path = [.allThreads]
default: break
}
if let hostId = argument("-host") {
@@ -105,6 +114,7 @@ final class AppModel {
mode = .paired(credentials)
feed = nil
feedError = nil
projects = nil
path = []
await enableNotifications()
}
@@ -113,6 +123,7 @@ final class AppModel {
mode = .demo
feed = nil
feedError = nil
projects = nil
path = []
}
@@ -122,6 +133,7 @@ final class AppModel {
mode = .unpaired
feed = nil
feedError = nil
projects = nil
path = []
}
@@ -241,6 +253,19 @@ final class AppModel {
self.feed = feed
}
// MARK: - All threads
/// `GET /v1/projects`, for the hosts and projects All threads lists.
func loadProjects() async {
guard mode != .unpaired else { return }
do {
projects = try await api.projects()
projectsError = nil
} catch {
projectsError = message(for: error)
}
}
func open(_ route: Route) {
path = [route]
}
@@ -316,6 +341,8 @@ final class AppModel {
extension AppModel {
/// A demo model with the fixture feed already loaded, for previews.
static func preview(path: [Route] = []) -> AppModel {
AppModel(mode: .demo, feed: DemoFixtures.previewFeed(), allRecent: DemoFixtures.previewRecent(), path: path)
AppModel(
mode: .demo, feed: DemoFixtures.previewFeed(), allRecent: DemoFixtures.previewRecent(),
projects: ProjectsResponse(hosts: DemoFixtures(now: .now).projectHosts), path: path)
}
}