Skip to content

Commit 8a3fcb8

Browse files
committed
Fixing Tahoe resize bug
1 parent 93fa08e commit 8a3fcb8

2 files changed

Lines changed: 50 additions & 15 deletions

File tree

singularity/AppDelegate.swift

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
165165
NotificationCenter.default.addObserver(forName: NSWindow.didChangeOcclusionStateNotification, object: nil, queue: .main) { [weak self] _ in Task { @MainActor in self?.enforceWindowSharingHidden() } }
166166
NotificationCenter.default.addObserver(forName: NSApplication.didUpdateNotification, object: nil, queue: .main) { [weak self] _ in Task { @MainActor in self?.enforceWindowSharingHidden() } }
167167
NotificationCenter.default.addObserver(forName: NSWindow.didResignKeyNotification, object: nil, queue: .main) { [weak self] _ in Task { @MainActor in self?.applyWindowAppearance() } }
168-
NotificationCenter.default.addObserver(forName: NSWindow.didResizeNotification, object: nil, queue: .main) { [weak self] _ in
169-
Task { @MainActor in
170-
self?.updateWindowMasks()
171-
self?.maybeCloseManualInputOnResize()
168+
NotificationCenter.default.addObserver(forName: NSWindow.didResizeNotification, object: nil, queue: .main) { [weak self] n in
169+
guard let self = self else { return }
170+
// Throttle mask updates during live resize to prevent layout thrashing
171+
// Only update if the window being resized is one we care about
172+
if let w = n.object as? NSWindow, w.level == .floating || w === self.mainPanelWindow {
173+
Task { @MainActor in
174+
self.updateWindowMasks()
175+
self.maybeCloseManualInputOnResize()
176+
}
172177
}
173178
}
174179
NotificationCenter.default.addObserver(forName: NSWindow.didChangeBackingPropertiesNotification, object: nil, queue: .main) { [weak self] _ in Task { @MainActor in self?.applyWindowAppearance(); self?.updateWindowMasks() } }
@@ -637,8 +642,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
637642
for view in candidates {
638643
collapseTitlebarView(view)
639644
}
645+
// CRITICAL FIX: Do NOT force layout here. This is called during resize and can cause
646+
// recursion or conflicts with AppKit's internal constraint solver (EXC_BAD_INSTRUCTION).
647+
// Just mark as needing layout and let the runloop handle it.
640648
container.needsLayout = true
641-
container.layoutSubtreeIfNeeded()
642649
}
643650

644651
private func collapseTitlebarView(_ view: NSView) {
@@ -653,15 +660,28 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
653660
frame.size.height = 0
654661
view.frame = frame
655662
}
656-
if objc_getAssociatedObject(view, &titlebarConstraintKey) as? NSLayoutConstraint == nil {
657-
view.translatesAutoresizingMaskIntoConstraints = false
658-
let constraint = view.heightAnchor.constraint(equalToConstant: 0)
659-
constraint.priority = NSLayoutConstraint.Priority(999)
660-
constraint.isActive = true
661-
objc_setAssociatedObject(view, &titlebarConstraintKey, constraint, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
662-
} else if let constraint = objc_getAssociatedObject(view, &titlebarConstraintKey) as? NSLayoutConstraint {
663-
constraint.constant = 0
664-
constraint.isActive = true
663+
664+
// Harden constraint logic: only touch constraints if strictly necessary
665+
if let constraint = objc_getAssociatedObject(view, &titlebarConstraintKey) as? NSLayoutConstraint {
666+
if constraint.constant != 0 {
667+
constraint.constant = 0
668+
}
669+
if !constraint.isActive {
670+
constraint.isActive = true
671+
}
672+
} else {
673+
// Check if we already have a zero-height constraint to avoid duplicates
674+
let existing = view.constraints.first { $0.firstAttribute == .height && $0.constant == 0 }
675+
if let existing = existing {
676+
if !existing.isActive { existing.isActive = true }
677+
objc_setAssociatedObject(view, &titlebarConstraintKey, existing, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
678+
} else {
679+
view.translatesAutoresizingMaskIntoConstraints = false
680+
let constraint = view.heightAnchor.constraint(equalToConstant: 0)
681+
constraint.priority = NSLayoutConstraint.Priority(999)
682+
constraint.isActive = true
683+
objc_setAssociatedObject(view, &titlebarConstraintKey, constraint, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
684+
}
665685
}
666686
view.subviews.forEach { collapseTitlebarView($0) }
667687
}

singularity/StartupUpdateCoordinator.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,22 @@ extension StartupUpdateCoordinator {
431431
return .skipped(.manifestUnavailable)
432432
}
433433
let decoder = JSONDecoder()
434-
decoder.dateDecodingStrategy = .iso8601
434+
let formatter = ISO8601DateFormatter()
435+
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
436+
decoder.dateDecodingStrategy = .custom { decoder in
437+
let container = try decoder.singleValueContainer()
438+
let string = try container.decode(String.self)
439+
if let date = formatter.date(from: string) {
440+
return date
441+
}
442+
// Fallback for no fractional seconds
443+
let formatter2 = ISO8601DateFormatter()
444+
formatter2.formatOptions = [.withInternetDateTime]
445+
if let date = formatter2.date(from: string) {
446+
return date
447+
}
448+
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expected date string to be ISO8601-formatted.")
449+
}
435450
let manifest = try decoder.decode(UpdateManifest.self, from: data)
436451
return .manifest(manifest)
437452
} catch {

0 commit comments

Comments
 (0)