fix: resolve duplicate resolution shortcuts, update freeze, and process id flashing

- Fix keyboard shortcut collision causing main window to intercept Enter/Escape keys when duplicate resolution sheet is open
- Fix UI freeze when checking release notes for an update by parsing HTML asynchronously
- Improve Sparkle changelog formatting by converting HTML tags to clean Markdown
- Change internal Process ID status message to Starting... when queueing downloads
- Fix EXC_BREAKPOINT crash on app launch by prioritizing Bundle.main over Bundle.module
- Update CHANGELOG.md for 0.6.2 release
This commit is contained in:
nimbold
2026-06-08 16:22:05 +03:30
parent ac7963e353
commit 34847b6234
5 changed files with 63 additions and 12 deletions
+9
View File
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.6.2] - 2026-06-08
### Fixes
- Fix keyboard shortcut collision that caused the main window to intercept Enter/Escape keys when the duplicate resolution sheet was open.
- Fix UI freeze when checking release notes for an update by parsing HTML asynchronously on a background thread.
- Improve Sparkle changelog formatting by converting HTML tags to clean Markdown instead of stripping them into an unreadable block of text.
- Change the internal `Process xxxxx` status message to a cleaner `Starting...` message when queueing a new download.
- Fix `EXC_BREAKPOINT` crash on app launch in production builds by prioritizing `Bundle.main` over `Bundle.module` when accessing resources.
## [0.6.1] - 2026-06-08
### New Features
+2 -2
View File
@@ -312,7 +312,7 @@ struct AddDownloadsView: View {
Button("Cancel") {
dismiss()
}
.keyboardShortcut(.cancelAction)
.keyboardShortcut(showingDuplicates ? nil : .cancelAction)
Button("Add to Queue") {
addDownloads(start: false)
@@ -324,7 +324,7 @@ struct AddDownloadsView: View {
}
.buttonStyle(.borderedProminent)
.disabled(!canAddDownloads)
.keyboardShortcut(.defaultAction)
.keyboardShortcut(showingDuplicates ? nil : .defaultAction)
}
}
+1 -1
View File
@@ -602,7 +602,7 @@ final class DownloadController: ObservableObject {
update(item.id) {
$0.rpcPort = handle.rpcPort
$0.rpcSecret = handle.rpcSecret
$0.message = "Process \(handle.processIdentifier)"
$0.message = "Starting..."
}
saveDownloads()
applySpeedLimitsToActiveDownloads()
+10 -2
View File
@@ -31,11 +31,19 @@ final class MediaEngineManager: ObservableObject {
}
func binaryPath(for addon: AddonType) -> URL? {
for bundle in [Bundle.main, Bundle.module] {
if let bundled = bundle.url(forResource: addon.binaryName, withExtension: nil),
if let bundled = Bundle.main.url(forResource: addon.binaryName, withExtension: nil),
FileManager.default.isExecutableFile(atPath: bundled.path) {
return bundled
}
// Prevent fatalError crash: avoid accessing Bundle.module if running in a packaged app.
if Bundle.main.bundleURL.pathExtension.lowercased() != "app" {
#if SWIFT_PACKAGE
if let bundled = Bundle.module.url(forResource: addon.binaryName, withExtension: nil),
FileManager.default.isExecutableFile(atPath: bundled.path) {
return bundled
}
#endif
}
return nil
}
@@ -32,19 +32,53 @@ class InlineUpdateUserDriver: NSObject, SPUUserDriver {
}
func showUpdateReleaseNotes(with downloadData: SPUDownloadData) {
DispatchQueue.main.async {
DispatchQueue.global(qos: .userInitiated).async {
if let htmlString = String(data: downloadData.data, encoding: .utf8) {
self.updater?.releaseNotes = self.stripHTML(htmlString)
let parsedText = self.fastHTMLToMarkdown(htmlString)
DispatchQueue.main.async {
self.updater?.releaseNotes = parsedText
}
}
}
}
private func stripHTML(_ string: String) -> String {
guard let data = string.data(using: .utf8) else { return string }
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) {
return attributedString.string.trimmingCharacters(in: .whitespacesAndNewlines)
nonisolated private func fastHTMLToMarkdown(_ html: String) -> String {
var text = html
text = text.replacingOccurrences(of: "<br>", with: "\n", options: .caseInsensitive)
text = text.replacingOccurrences(of: "<br/>", with: "\n", options: .caseInsensitive)
text = text.replacingOccurrences(of: "<br />", with: "\n", options: .caseInsensitive)
text = text.replacingOccurrences(of: "</p>", with: "\n\n", options: .caseInsensitive)
text = text.replacingOccurrences(of: "<li>", with: "- ", options: .caseInsensitive)
text = text.replacingOccurrences(of: "</li>", with: "\n", options: .caseInsensitive)
text = text.replacingOccurrences(of: "<h1>", with: "# ", options: .caseInsensitive)
text = text.replacingOccurrences(of: "</h1>", with: "\n\n", options: .caseInsensitive)
text = text.replacingOccurrences(of: "<h2>", with: "## ", options: .caseInsensitive)
text = text.replacingOccurrences(of: "</h2>", with: "\n\n", options: .caseInsensitive)
text = text.replacingOccurrences(of: "<h3>", with: "### ", options: .caseInsensitive)
text = text.replacingOccurrences(of: "</h3>", with: "\n\n", options: .caseInsensitive)
text = text.replacingOccurrences(of: "<b>", with: "**", options: .caseInsensitive)
text = text.replacingOccurrences(of: "</b>", with: "**", options: .caseInsensitive)
text = text.replacingOccurrences(of: "<strong>", with: "**", options: .caseInsensitive)
text = text.replacingOccurrences(of: "</strong>", with: "**", options: .caseInsensitive)
if let regex = try? NSRegularExpression(pattern: "<[^>]+>", options: .caseInsensitive) {
let range = NSRange(location: 0, length: text.utf16.count)
text = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "")
}
return string
text = text.replacingOccurrences(of: "&nbsp;", with: " ")
text = text.replacingOccurrences(of: "&amp;", with: "&")
text = text.replacingOccurrences(of: "&lt;", with: "<")
text = text.replacingOccurrences(of: "&gt;", with: ">")
text = text.replacingOccurrences(of: "&quot;", with: "\"")
text = text.replacingOccurrences(of: "&#39;", with: "'")
if let regex = try? NSRegularExpression(pattern: "\\n{3,}", options: []) {
let range = NSRange(location: 0, length: text.utf16.count)
text = regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "\n\n")
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
}
func showUpdateReleaseNotesFailedToDownloadWithError(_ error: Error) {