Watch: snooze finished threads, name the files a file approval would edit, Settle on finished notifications, an instant launch from the saved feed, plain Loading states, and no more cancelled errors
Gateway / check (push) Successful in 9s
Gateway / image (push) Successful in 22s

Gateway: an approval push for a file change says which files it would edit
This commit is contained in:
gib committed 2026-11-02 14:45:34 -05:00
1 parent 4dc4e57dac
commit edc4b88990
35 files changed
+1162 -144

No files matched your search

@@ -1,6 +1,6 @@
// Notification categories and actions from docs/api.md, and what each action does. Allow, Deny,
// Reply and a question's answers run in the background against the gateway; tapping a notification
// opens its thread.
// Reply, Settle and a question's answers run in the background against the gateway; tapping a
// notification opens its thread.
//
// An `input` push that carries `question` gets one button per option, plus "Say an answer" when the
// question takes its own answer. watchOS only lets a notification's actions change per push from
@@ -18,6 +18,7 @@ enum NotificationCommand: Equatable, Sendable {
/// One answer to a push's single question: an option's value, or what was said or typed.
case answer(hostId: String, threadId: String, requestId: String, questionId: String, value: String)
case reply(hostId: String, threadId: String, text: String)
case settle(hostId: String, threadId: String)
case open(Route?)
case none
@@ -25,6 +26,7 @@ enum NotificationCommand: Equatable, Sendable {
static let allow = "allow"
static let deny = "deny"
static let reply = "reply"
static let settle = "settle"
static let open = "open"
/// Opens the thread, on a question the notification cannot answer itself.
static let answer = "answer"
@@ -64,6 +66,9 @@ enum NotificationCommand: Equatable, Sendable {
let text = text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard let hostId, let threadId, !text.isEmpty else { self = .open(thread); return }
self = .reply(hostId: hostId, threadId: threadId, text: text)
case Action.settle:
guard let hostId, let threadId else { self = .open(thread); return }
self = .settle(hostId: hostId, threadId: threadId)
case Action.say:
self = answer(text)
case let id where id.hasPrefix(Action.optionPrefix):
@@ -110,6 +115,7 @@ final class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
identifier: Action.reply, title: "Reply", options: [],
textInputButtonTitle: "Send", textInputPlaceholder: "Reply")
let answer = UNNotificationAction(identifier: Action.answer, title: "Answer", options: [.foreground])
let settle = UNNotificationAction(identifier: Action.settle, title: "Settle", options: [])
func category(_ id: PushCategory, _ actions: [UNNotificationAction]) -> UNNotificationCategory {
UNNotificationCategory(identifier: id.rawValue, actions: actions, intentIdentifiers: [], options: [])
@@ -118,7 +124,7 @@ final class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
category(.approval, [allow, deny]),
// A question push replaces these with its options; see `inputActions`.
category(.input, [answer, open]),
category(.finished, [reply, open]),
category(.finished, [reply, settle, open]),
category(.failed, [open]),
category(.host, []),
category(.test, []),
@@ -180,6 +186,11 @@ final class NotificationHandler: NSObject, UNUserNotificationCenterDelegate {
await attempt("Couldn't send your reply") {
try await model.api.sendMessage(hostId: hostId, threadId: threadId, text: text)
}
case let .settle(hostId, threadId):
guard model.mode != .unpaired else { return }
await attempt("Couldn't settle") {
try await model.api.settle(hostId: hostId, threadId: threadId)
}
}
model.reloadComplication()
await model.refreshFeed()