Watch: status, environment and notifications screens

A gear on the feed opens a status overview that leads with what is wrong:
offline hosts, hosts on a fallback route, tokens expiring within 7 days,
and push problems, with healthy hosts folded into one row. Each host opens
its route ladder with Reconnect. The notifications page shows registration,
the last push, Send test and Unpair. Demo mode gains a Laptop on its VPN
fallback, and snapshot tests render the three screens.
This commit is contained in:
gib committed 2026-11-01 15:16:18 -05:00
1 parent 9707daf1fe
commit 7ca0362745
17 files changed
+1063 -14

No files matched your search

+22 -1
View File
@@ -1,4 +1,5 @@
// Mock B colors and the shared card. Amber means it needs you, teal means working, grey means done.
// Mock B colors and the shared card. Amber means it needs you, teal means working, grey means done,
// and red means broken.
import SwiftUI
@@ -10,6 +11,7 @@ enum Theme {
static let control = Color(red: 0.173, green: 0.173, blue: 0.180) // #2c2c2e
static let secondaryText = Color(red: 0.682, green: 0.682, blue: 0.698) // #aeaeb2
static let bodyText = Color(red: 0.898, green: 0.898, blue: 0.918) // #e5e5ea
static let bad = Color(red: 1.0, green: 0.420, blue: 0.369) // #ff6b5e
}
extension Phase {
@@ -87,6 +89,25 @@ enum Format {
return "\(hours / 24) d"
}
/// How long until a date, such as "40 s", "3 min" or "2 h".
static func countdown(to date: Date, now: Date = .now) -> String {
let seconds = max(0, Int(date.timeIntervalSince(now).rounded()))
if seconds < 60 { return "\(seconds) s" }
if seconds < 3600 { return "\(seconds / 60) min" }
return "\(seconds / 3600) h"
}
/// "expires in 29 days", "expires today" or "expired 2 days ago", for a token.
static func expiry(_ date: Date, now: Date = .now) -> String {
let days = Int((date.timeIntervalSince(now) / 86_400).rounded(.down))
if date < now {
let ago = Int(now.timeIntervalSince(date) / 86_400)
return ago < 1 ? "expired today" : "expired \(ago) day\(ago == 1 ? "" : "s") ago"
}
if days < 1 { return "expires today" }
return "expires in \(days) day\(days == 1 ? "" : "s")"
}
/// A model id made readable: `claude-opus-5-5` becomes "Claude Opus 5.5".
static func modelName(_ id: String) -> String {
var words: [String] = []