feat(panel-snapshot): add five offscreen panels for update states

Render panels 10-14 showing the update check states:
- panel-10-update-idle: menu with 3 captures, no update available
- panel-11-update-checking: same as 10, but row reads "Checking..."
- panel-12-update-uptodate: footer status shows "Redline X.Y.Z is up to date, checked 10:42 Dubai"
- panel-13-update-staged: row "Update to 9.9.9" present, footer status "Update to 9.9.9 is ready"
- panel-14-update-revert: row "Revert to 0.2.0" present

All five panels driven by model state only; never touch real appcast or UserDefaults.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ZiTXPbPCSjzPVsfoweAbp
This commit is contained in:
Claude Fable 5
2026-09-05 11:01:28 +04:00
parent 169dd2b32c
commit e71c2e7c91
+65
View File
@@ -155,6 +155,37 @@ enum PanelSnapshot {
model.replaceRegion(sampleRegion())
try await addSampleCaptures(to: model)
try renderMenuBar(model: model, to: directory, name: "09-captures-present-onedrive")
// 10 update idle: 3 captures, no update available, footer "Redline <ver>", row "Check for updates".
let (updateModel, updateRoot) = try makeIsolatedModel()
defer { try? FileManager.default.removeItem(at: updateRoot) }
updateModel.snapshotSetScreenRecordingGranted(true)
updateModel.replaceRegion(sampleRegion())
try await addSampleCaptures(to: updateModel)
// No update set, updateChecker in idle state, no previous version
updateModel.snapshotSetPreviousVersion(nil)
try renderMenuBar(model: updateModel, to: directory, name: "10-update-idle")
// 11 update checking: same as 10 but isCheckingForUpdates = true.
updateModel.snapshotSetIsCheckingForUpdates(true)
try renderMenuBar(model: updateModel, to: directory, name: "11-update-checking")
updateModel.snapshotSetIsCheckingForUpdates(false)
// 12 update up-to-date: footer status line reads "Redline <ver> is up to date, checked 10:42 Dubai".
let upToDateMessage = "Redline \(updateModel.appVersion) is up to date, checked 10:42 Dubai"
updateModel.snapshotSetUpdateStatusMessage(upToDateMessage)
try renderMenuBar(model: updateModel, to: directory, name: "12-update-uptodate")
// 13 update staged: row "Update to 9.9.9" present, footer status "Update to 9.9.9 is ready".
updateModel.snapshotSetUpdateAvailable(version: "9.9.9", notes: "Test release")
updateModel.snapshotSetUpdateStatusMessage("Update to 9.9.9 is ready")
try renderMenuBar(model: updateModel, to: directory, name: "13-update-staged")
// 14 update revert: row "Revert to 0.2.0" present.
updateModel.snapshotSetUpdateAvailable(version: nil, notes: nil) // Clear the staged update
updateModel.snapshotSetUpdateStatusMessage(nil)
updateModel.snapshotSetPreviousVersion("0.2.0")
try renderMenuBar(model: updateModel, to: directory, name: "14-update-revert")
}
@MainActor
@@ -360,6 +391,40 @@ extension AppModel {
)
self[keyPath: writable] = granted
}
/// Snapshot-only: set isCheckingForUpdates without triggering a real check.
func snapshotSetIsCheckingForUpdates(_ checking: Bool) {
let writable: ReferenceWritableKeyPath<AppModel, Bool> = unsafeBitCast(
\AppModel.isCheckingForUpdates, to: ReferenceWritableKeyPath<AppModel, Bool>.self
)
self[keyPath: writable] = checking
}
/// Snapshot-only: set updateStatusMessage (statusLine alias) for panel display.
func snapshotSetUpdateStatusMessage(_ message: String?) {
setStatus(message)
}
/// Snapshot-only: set updateAvailable without triggering a real download.
func snapshotSetUpdateAvailable(version: String?, notes: String?) {
if let version = version, let notes = notes {
let writable: ReferenceWritableKeyPath<AppModel, (version: String, notes: String)?> = unsafeBitCast(
\AppModel.updateAvailable, to: ReferenceWritableKeyPath<AppModel, (version: String, notes: String)?>.self
)
self[keyPath: writable] = (version: version, notes: notes)
} else {
let writable: ReferenceWritableKeyPath<AppModel, (version: String, notes: String)?> = unsafeBitCast(
\AppModel.updateAvailable, to: ReferenceWritableKeyPath<AppModel, (version: String, notes: String)?>.self
)
self[keyPath: writable] = nil
}
}
/// Snapshot-only: set a fake previousVersion for the revert panel.
func snapshotSetPreviousVersion(_ version: String?) {
updateChecker.snapshotPreviousVersionOverride = version
updateChecker.snapshotUsesPreviousVersionOverride = true
}
}
private enum SnapshotError: Error, CustomStringConvertible {