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

+56 -12
View File
@@ -27,12 +27,7 @@ actor DemoGateway: GatewayAPI {
}
func feed() async throws -> Feed {
let items = items.filter { !settled.contains($0.id) }
let needsYou = items.filter(\.phase.needsYou).sorted { $0.updatedAt < $1.updatedAt }
let working = items.filter(\.phase.isWorking).sorted { $0.updatedAt > $1.updatedAt }
let recent = items.filter { $0.phase == .completed || $0.phase == .failed }
.sorted { $0.updatedAt > $1.updatedAt }
return Feed(needsYou: needsYou, working: working, recent: Array(recent.prefix(10)), hosts: hosts)
DemoFixtures.feed(items.filter { !settled.contains($0.id) }, hosts: hosts)
}
func summary() async throws -> Summary {
@@ -93,6 +88,16 @@ actor DemoGateway: GatewayAPI {
settled.insert(item.id)
}
func recent() async throws -> RecentResponse {
RecentResponse(items: DemoFixtures.recent(items.filter { !settled.contains($0.id) }))
}
func settleRecent() async throws -> SettleRecentResponse {
let completed = DemoFixtures.recent(items.filter { !settled.contains($0.id) }).filter { $0.phase == .completed }
settled.formUnion(completed.map(\.id))
return SettleRecentResponse(settled: completed.count, failed: 0)
}
func projects() async throws -> ProjectsResponse {
ProjectsResponse(hosts: projectHosts)
}
@@ -254,7 +259,28 @@ struct DemoFixtures {
projectTitle: "Panama", title: "Fix hyprlock crash", phase: .completed, updatedAt: ago(95),
lastMessage: "Fixed. hyprlock no longer crashes when a monitor sleeps.", request: nil)
items = [approval, question, release, homelab, scaffold, containerfile, statusScreens, nextjs, hyprlock, cdn]
// Older finished threads, past the feed's first 10 in Recent. The gateway has not read most of
// them, so they have no last message.
func older(_ host: HostSummary, _ id: String, _ project: String, _ title: String, hours: Double, failed: Bool = false) -> FeedItem {
FeedItem(
hostId: host.id, hostLabel: host.label, threadId: id, projectId: "p-\(project)", projectTitle: project,
title: title, phase: failed ? .failed : .completed, updatedAt: ago(hours * 60), lastMessage: nil, request: nil)
}
let (mac, server, laptop) = (hosts[0], hosts[1], hosts[2])
let olderRecent = [
older(laptop, "t-icons", "wristcode", "App icon variants", hours: 4),
older(mac, "t-receipts", "bill-tracker", "Receipt upload form", hours: 6),
older(server, "t-backup", "homelab", "Nightly restic backup", hours: 9, failed: true),
older(mac, "t-tailwind", "gbrown.org", "Tailwind 5 migration", hours: 20),
older(server, "t-caddy", "homelab", "Caddy wildcard certs", hours: 27),
older(laptop, "t-readme", "wristcode", "README pass", hours: 30),
older(mac, "t-csv", "bill-tracker", "CSV export", hours: 50),
older(server, "t-immich", "homelab", "Immich upgrade", hours: 72),
older(mac, "t-lint", "cdn", "Clippy warnings", hours: 96),
older(laptop, "t-pair", "wristcode", "Pairing code expiry", hours: 120),
]
items = [approval, question, release, homelab, scaffold, containerfile, statusScreens, nextjs, hyprlock, cdn] + olderRecent
func m(_ id: String, _ role: Message.Role, _ minutes: Double, _ text: String) -> Message {
Message(id: id, role: role, text: text, createdAt: ago(minutes))
@@ -359,13 +385,31 @@ struct DemoFixtures {
device: .init(name: "Apple Watch", pairedAt: days(-3), pushRegistered: true, apnsEnvironment: .production))
}
/// The feed the gateway builds from these threads: needs-you oldest first, working and Recent
/// newest first, and only the first 10 of Recent.
static func feed(_ items: [FeedItem], hosts: [HostSummary]) -> Feed {
let recent = recent(items)
return Feed(
needsYou: items.filter(\.phase.needsYou).sorted { $0.updatedAt < $1.updatedAt },
working: items.filter(\.phase.isWorking).sorted { $0.updatedAt > $1.updatedAt },
recent: Array(recent.prefix(10)),
recentTotal: recent.count,
hosts: hosts)
}
/// Every finished or failed thread, newest first, as `GET /v1/recent` lists them.
static func recent(_ items: [FeedItem]) -> [FeedItem] {
items.filter { $0.phase == .completed || $0.phase == .failed }.sorted { $0.updatedAt > $1.updatedAt }
}
/// A feed for previews, without going through the actor.
static func previewFeed(now: Date = .now) -> Feed {
let fixtures = DemoFixtures(now: now)
return Feed(
needsYou: fixtures.items.filter(\.phase.needsYou),
working: fixtures.items.filter(\.phase.isWorking),
recent: fixtures.items.filter { !$0.phase.needsYou && !$0.phase.isWorking },
hosts: fixtures.hosts)
return feed(fixtures.items, hosts: fixtures.hosts)
}
/// The full Recent list for previews.
static func previewRecent(now: Date = .now) -> [FeedItem] {
recent(DemoFixtures(now: now).items)
}
}