Watch: keep Recent past 10 behind an N more row with a full list, and settle all in one call

This commit is contained in:
gib committed 2026-11-01 22:32:19 -05:00
1 parent 2a127327bf
commit a5222e14e8
13 files changed
+384 -52

No files matched your search

+12
View File
@@ -51,6 +51,10 @@ protocol GatewayAPI: Sendable {
func stop(hostId: String, threadId: String) async throws
/// Settles a thread, as in T3. A settled thread that finished or failed leaves the feed.
func settle(hostId: String, threadId: String) async throws
/// Every unsettled finished thread, including those past the feed's first 10.
func recent() async throws -> RecentResponse
/// Settles every completed thread in Recent. Failed threads stay.
func settleRecent() async throws -> SettleRecentResponse
func projects() async throws -> ProjectsResponse
func newThread(_ request: NewThreadRequest) async throws -> NewThreadResponse
func registerPush(_ registration: PushRegistration) async throws
@@ -101,6 +105,14 @@ struct GatewayClient: GatewayAPI {
try await callIgnoringBody("POST", ["v1", "threads", hostId, threadId, "settle"], body: nil as SendMessageRequest?)
}
func recent() async throws -> RecentResponse {
try await call("GET", ["v1", "recent"])
}
func settleRecent() async throws -> SettleRecentResponse {
try await call("POST", ["v1", "recent", "settle"])
}
func projects() async throws -> ProjectsResponse {
try await call("GET", ["v1", "projects"])
}
+43
View File
@@ -167,8 +167,51 @@ struct FeedItem: Codable, Hashable, Sendable, Identifiable {
struct Feed: Codable, Hashable, Sendable {
var needsYou: [FeedItem]
var working: [FeedItem]
/// Finished or failed threads not settled yet, newest first and at most 10.
var recent: [FeedItem]
/// How many threads Recent holds in all. `GET /v1/recent` lists the ones past `recent`.
var recentTotal: Int
var hosts: [HostSummary]
/// `recentTotal` defaults to the threads in `recent`, as when nothing is left out.
init(needsYou: [FeedItem], working: [FeedItem], recent: [FeedItem], recentTotal: Int? = nil, hosts: [HostSummary]) {
self.needsYou = needsYou
self.working = working
self.recent = recent
self.recentTotal = recentTotal ?? recent.count
self.hosts = hosts
}
/// Recent threads the feed leaves out, which the "N more" row counts.
var moreRecent: Int { max(0, recentTotal - recent.count) }
}
extension Feed {
private enum CodingKeys: String, CodingKey {
case needsYou, working, recent, recentTotal, hosts
}
/// `recentTotal` is newer than the rest, so a gateway that leaves it out has nothing past `recent`.
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.init(
needsYou: try container.decode([FeedItem].self, forKey: .needsYou),
working: try container.decode([FeedItem].self, forKey: .working),
recent: try container.decode([FeedItem].self, forKey: .recent),
recentTotal: try container.decodeIfPresent(Int.self, forKey: .recentTotal),
hosts: try container.decode([HostSummary].self, forKey: .hosts))
}
}
/// `GET /v1/recent`: every unsettled finished thread, newest first. Older ones may lack `lastMessage`.
struct RecentResponse: Codable, Hashable, Sendable {
var items: [FeedItem]
}
/// `POST /v1/recent/settle`. `failed` counts threads T3 refused or whose host was offline.
struct SettleRecentResponse: Codable, Hashable, Sendable {
var settled: Int
var failed: Int
}
/// `GET /v1/summary`: the counts behind the complication.
+7 -1
View File
@@ -5,6 +5,8 @@ import Foundation
enum Route: Hashable, Sendable {
case thread(hostId: String, threadId: String)
case newThread
/// Every thread in Recent, past the feed's first 10.
case recent
/// The status overview, and from it one host or the notifications page.
case status
case host(id: String)
@@ -12,7 +14,7 @@ enum Route: Hashable, Sendable {
static let scheme = "wristcode"
/// `wristcode://thread/<hostId>/<threadId>`, `wristcode://new`, `wristcode://status`,
/// `wristcode://thread/<hostId>/<threadId>`, `wristcode://new`, `wristcode://recent`, `wristcode://status`,
/// `wristcode://host/<hostId>` or `wristcode://notifications`.
var url: URL {
var components = URLComponents()
@@ -25,6 +27,8 @@ enum Route: Hashable, Sendable {
.joined(separator: "/")
case .newThread:
components.host = "new"
case .recent:
components.host = "recent"
case .status:
components.host = "status"
case let .host(id):
@@ -47,6 +51,8 @@ enum Route: Hashable, Sendable {
self = .thread(hostId: parts[0], threadId: parts[1])
case "new":
self = .newThread
case "recent":
self = .recent
case "status":
self = .status
case "host" where parts.count == 1 && !parts[0].isEmpty: