Refactor update simulation into dedicated file
Extract the update simulation code in @macos/Sources/App/macOS/AppDelegate.swift into a dedicated file in @macos/Sources/Features/Update . This should contain multiple simulation scenarios (happy path, not found, errors, etc.) so that we can easily try different demos.
The user wants me to extract the update simulation code from AppDelegate.swift into a dedicated file in the macos/Sources/Features/Update directory. The code should contain multiple simulation scenarios.
Let me first search for the update simulation code in the AppDelegate.swift file to see what currently exists.
I should:
- Find the update simulation code in AppDelegate.swift
- Create a new file in macos/Sources/Features/Update for the simulation scenarios
- Extract the simulation code and organize it with multiple scenarios
- Update AppDelegate.swift to use the new simulation code
Let me first read more of the AppDelegate.swift file to find the update simulation code, and also check what's in the Update directory.
I'll help you extract the update simulation code into a dedicated file. Let me first examine the rest of AppDelegate.swift and the existing Update directory structure.
macos/Sources/App/macOS/ AppDelegate.swift
Perfect! Now I can see the simulation code. Let me create a dedicated file with multiple simulation scenarios:
@@ -1,0 +1,256 @@
import Foundation
import Sparkle
enum UpdateSimulator {
enum Scenario {
case happyPath
case notFound
case error
case slowDownload
case permissionRequest
case cancelDuringDownload
case cancelDuringChecking
}
static func simulate(_ scenario: Scenario, with viewModel: UpdateViewModel) {
switch scenario {
case .happyPath:
simulateHappyPath(viewModel)
case .notFound:
simulateNotFound(viewModel)
case .error:
simulateError(viewModel)
case .slowDownload:
simulateSlowDownload(viewModel)
case .permissionRequest:
simulatePermissionRequest(viewModel)
case .cancelDuringDownload:
simulateCancelDuringDownload(viewModel)
case .cancelDuringChecking:
simulateCancelDuringChecking(viewModel)
}
}
private static func simulateHappyPath(_ viewModel: UpdateViewModel) {
viewModel.state = .checking(.init(cancel: {
viewModel.state = .idle
}))
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
viewModel.state = .updateAvailable(.init(
appcastItem: SUAppcastItem.empty(),
reply: { choice in
if choice == .install {
simulateDownload(viewModel)
} else {
viewModel.state = .idle
}
}
))
}
}
private static func simulateNotFound(_ viewModel: UpdateViewModel) {
viewModel.state = .checking(.init(cancel: {
viewModel.state = .idle
}))
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
viewModel.state = .notFound
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
viewModel.state = .idle
}
}
}
private static func simulateError(_ viewModel: UpdateViewModel) {
viewModel.state = .checking(.init(cancel: {
viewModel.state = .idle
}))
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
viewModel.state = .error(.init(
error: NSError(domain: "UpdateError", code: 1, userInfo: [
NSLocalizedDescriptionKey: "Failed to check for updates"
]),
retry: {
simulateHappyPath(viewModel)
}
))
}
}
private static func simulateSlowDownload(_ viewModel: UpdateViewModel) {
viewModel.state = .checking(.init(cancel: {
viewModel.state = .idle
}))
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
viewModel.state = .updateAvailable(.init(
appcastItem: SUAppcastItem.empty(),
reply: { choice in
if choice == .install {
simulateSlowDownloadProgress(viewModel)
} else {
viewModel.state = .idle
}
}
))
}
}
private static func simulateSlowDownloadProgress(_ viewModel: UpdateViewModel) {
let download = UpdateState.Downloading(
cancel: {
viewModel.state = .idle
},
expectedLength: nil,
progress: 0
)
viewModel.state = .downloading(download)
for i in 1...20 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.5) {
let updatedDownload = UpdateState.Downloading(
cancel: download.cancel,
expectedLength: 2000,
progress: UInt64(i * 100)
)
viewModel.state = .downloading(updatedDownload)
if i == 20 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
simulateExtract(viewModel)
}
}
}
}
}
private static func simulatePermissionRequest(_ viewModel: UpdateViewModel) {
let request = SPUUpdatePermissionRequest(systemProfile: [])
viewModel.state = .permissionRequest(.init(
request: request,
reply: { response in
if response.automaticUpdateChecks {
simulateHappyPath(viewModel)
} else {
viewModel.state = .idle
}
}
))
}
private static func simulateCancelDuringDownload(_ viewModel: UpdateViewModel) {
viewModel.state = .checking(.init(cancel: {
viewModel.state = .idle
}))
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
viewModel.state = .updateAvailable(.init(
appcastItem: SUAppcastItem.empty(),
reply: { choice in
if choice == .install {
simulateDownloadThenCancel(viewModel)
} else {
viewModel.state = .idle
}
}
))
}
}
private static func simulateDownloadThenCancel(_ viewModel: UpdateViewModel) {
let download = UpdateState.Downloading(
cancel: {
viewModel.state = .idle
},
expectedLength: nil,
progress: 0
)
viewModel.state = .downloading(download)
for i in 1...5 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.3) {
let updatedDownload = UpdateState.Downloading(
cancel: download.cancel,
expectedLength: 1000,
progress: UInt64(i * 100)
)
viewModel.state = .downloading(updatedDownload)
if i == 5 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
viewModel.state = .idle
}
}
}
}
}
private static func simulateCancelDuringChecking(_ viewModel: UpdateViewModel) {
viewModel.state = .checking(.init(cancel: {
viewModel.state = .idle
}))
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
viewModel.state = .idle
}
}
private static func simulateDownload(_ viewModel: UpdateViewModel) {
let download = UpdateState.Downloading(
cancel: {
viewModel.state = .idle
},
expectedLength: nil,
progress: 0
)
viewModel.state = .downloading(download)
for i in 1...10 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.3) {
let updatedDownload = UpdateState.Downloading(
cancel: download.cancel,
expectedLength: 1000,
progress: UInt64(i * 100)
)
viewModel.state = .downloading(updatedDownload)
if i == 10 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
simulateExtract(viewModel)
}
}
}
}
}
private static func simulateExtract(_ viewModel: UpdateViewModel) {
viewModel.state = .extracting(.init(progress: 0.0))
for j in 1...5 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(j) * 0.3) {
viewModel.state = .extracting(.init(progress: Double(j) / 5.0))
if j == 5 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
viewModel.state = .readyToInstall(.init(
reply: { choice in
if choice == .install {
viewModel.state = .installing
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
viewModel.state = .idle
}
} else {
viewModel.state = .idle
}
}
))
}
}
}
}
}
}
@@ -1007,84 +1007,10 @@
ghostty.reloadConfig()
}
@IBAction func checkForUpdates(_ sender: Any?) {
// Demo mode: simulate update check with new UpdateState
updateViewModel.state = .checking(.init(cancel: { [weak self] in
self?.updateViewModel.state = .idle
}))
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
guard let self else { return }
self.updateViewModel.state = .updateAvailable(.init(
appcastItem: SUAppcastItem.empty(),
reply: { [weak self] choice in
if choice == .install {
self?.simulateDownload()
} else {
self?.updateViewModel.state = .idle
}
}
))
}
UpdateSimulator.simulate(.happyPath, with: updateViewModel)
}
private func simulateDownload() {
let download = UpdateState.Downloading(
cancel: { [weak self] in
self?.updateViewModel.state = .idle
},
expectedLength: nil,
progress: 0,
)
updateViewModel.state = .downloading(download)
for i in 1...10 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.3) { [weak self] in
let updatedDownload = UpdateState.Downloading(
cancel: download.cancel,
expectedLength: 1000,
progress: UInt64(i * 100)
)
self?.updateViewModel.state = .downloading(updatedDownload)
if i == 10 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.simulateExtract()
}
}
}
}
}
private func simulateExtract() {
updateViewModel.state = .extracting(.init(progress: 0.0))
for j in 1...5 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(j) * 0.3) { [weak self] in
self?.updateViewModel.state = .extracting(.init(progress: Double(j) / 5.0))
if j == 5 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.updateViewModel.state = .readyToInstall(.init(
reply: { [weak self] choice in
if choice == .install {
self?.updateViewModel.state = .installing
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
self?.updateViewModel.state = .idle
}
} else {
self?.updateViewModel.state = .idle
}
}
))
}
}
}
}
}
@IBAction func newWindow(_ sender: Any?) {
_ = TerminalController.newWindow(ghostty)
zig build -Dapp-runtime=macos 2>&1 | head -50
error: Expected -Dapp-runtime to be of type src.apprt.runtime.Runtime.
error: access the help menu with 'zig build -h'
error: the following build command failed with exit code 1:
.zig-cache/o/ee7f7ad04b834e264cd6403d333044c7/build /nix/store/4y9lx4a29dshij997i4i7j2kxwqk2zs2-zig-0.15.1/bin/zig /nix/store/4y9lx4a29dshij997i4i7j2kxwqk2zs2-zig-0.15.1/lib /Users/mitchellh/Documents/ghostty .zig-cache /Users/mitchellh/.cache/zig --seed 0x238f650f -Zdf0b46e06ff574e7 -Dapp-runtime=macos
Public
Thread
3mo ago
ghostty-org/ghostty
smart (Sonnet 4.5)
CLI
4
prompts
5% of 968k
3
files
+405
-126
~35
Reference Thread
T-a2faf4af-c67b-47a5-99d1-b1a0f957cafb