53b4f60da1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.2 KiB
Swift
68 lines
2.2 KiB
Swift
import Foundation
|
|
|
|
// What the iPhone shows for one service Mochi (GitHub, Stripe, Vercel, Resend,
|
|
// Cal.com, n8n, Notion): its state dot, why it has that color, and the latest
|
|
// items the Mac fetched. The Mac builds it from the data its pollers already
|
|
// keep (ServicePublisher) and writes it to the private iCloud zone, encrypted;
|
|
// the iPhone only reads it. No API key ever leaves the Mac.
|
|
|
|
enum ServiceTone: String, Codable, Sendable {
|
|
case ok, warning, error, info, idle
|
|
}
|
|
|
|
struct ServiceItem: Codable, Equatable, Sendable {
|
|
var title: String
|
|
var detail: String = ""
|
|
var tone: ServiceTone = .idle
|
|
var date: Date? = nil
|
|
var url: String? = nil
|
|
}
|
|
|
|
struct ServiceSection: Codable, Equatable, Sendable {
|
|
var title: String
|
|
var items: [ServiceItem]
|
|
}
|
|
|
|
struct ServiceSnapshot: Codable, Equatable, Sendable {
|
|
var pillId: String
|
|
var tone: ServiceTone
|
|
/// One line under the name ("Balance 1 240,00 EUR", "2 pull requests").
|
|
var headline: String
|
|
/// Why the dot has its color ("CI failing on coucou · main").
|
|
var reason: String
|
|
var sections: [ServiceSection]
|
|
var updatedAt: Date
|
|
|
|
static let recordType = "Service"
|
|
|
|
static func recordName(for pillId: String) -> String { "service-\(pillId)" }
|
|
|
|
static func pillId(fromRecordName name: String) -> String? {
|
|
name.hasPrefix("service-") ? String(name.dropFirst("service-".count)) : nil
|
|
}
|
|
|
|
/// Equal apart from the date: nothing to write again.
|
|
func sameContent(as other: ServiceSnapshot) -> Bool {
|
|
var a = self, b = other
|
|
a.updatedAt = .distantPast
|
|
b.updatedAt = .distantPast
|
|
return a == b
|
|
}
|
|
}
|
|
|
|
extension PillCatalog {
|
|
/// The service Mochi the iPhone shows, in this order.
|
|
static let phoneServices = [
|
|
"integration_github", "integration_stripe", "integration_vercel", "integration_resend",
|
|
"integration_calcom", "integration_n8n", "integration_notion",
|
|
]
|
|
|
|
/// Agent sessions (VS Code, Cursor, Codex…), as opposed to services and chat providers.
|
|
static func isSession(_ id: String) -> Bool {
|
|
switch definition(for: id)?.category {
|
|
case .service, .ai: false
|
|
default: true
|
|
}
|
|
}
|
|
}
|