feat: surface check/revert/version in the menu and badge the icon

AppModel exposes appVersion, previousVersion, isCheckingForUpdates and
updateStatusMessage (an alias for the existing statusLine plumbing — one
status channel, not a new one), plus checkForUpdates() and
revertToPreviousVersion() wired to the hardened UpdateChecker.

Menu gains, in order: "Update to X" (unchanged, staged-only), "Check for
updates" (labelled "Checking…" and disabled mid-check), "Revert to <version>"
(only when a rollback copy exists), then the existing rows unchanged, then a
non-interactive footer "Redline <version>" with the status line under it —
same caption/secondary styles already used elsewhere in the file, no new
tokens.

Menu-bar icon gets a small badge while an update is staged: uses the SF
Symbol's own ".badge" variant when one exists for the current icon, otherwise
overlays a small dot on the plain symbol. Reads live model state, so the
badge disappears on its own once the offer clears.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 10:00:17 +04:00
co-authored by Claude Fable 5.1
parent a79a569a7d
commit 064e410e30
3 changed files with 82 additions and 1 deletions
+31
View File
@@ -15,6 +15,8 @@ struct MenuBarView: View {
Divider()
returnsBlock
}
Divider()
updateFooter
}
.padding(10)
.frame(width: 320, alignment: .leading)
@@ -83,6 +85,21 @@ struct MenuBarView: View {
}
}
Button {
model.checkForUpdates()
} label: {
actionLabel(model.isCheckingForUpdates ? "Checking…" : "Check for updates")
}
.disabled(model.isCheckingForUpdates)
if let previous = model.previousVersion {
Button {
model.revertToPreviousVersion()
} label: {
actionLabel("Revert to \(previous)")
}
}
Button {
let anchor = NSApp.keyWindow?.contentView
if let sender = model as? SendCapable {
@@ -193,4 +210,18 @@ struct MenuBarView: View {
private var newestReturns: [ReturnedDocument] {
model.allReturns.sorted { $0.detectedAt > $1.detectedAt }
}
private var updateFooter: some View {
VStack(alignment: .leading, spacing: 2) {
Text("Redline \(model.appVersion)")
.font(.caption)
.foregroundStyle(.secondary)
if let message = model.updateStatusMessage {
Text(message)
.font(.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
}
}