Skip to content

Swift 6 language mode and a consistent macOS 14 baseline - #64

Open
matijazezelj wants to merge 4 commits into
AlexStrNik:mainfrom
matijazezelj:chore/swift6-macos14-baseline
Open

matijazezelj wants to merge 4 commits into
AlexStrNik:mainfrom
matijazezelj:chore/swift6-macos14-baseline

Conversation

@matijazezelj

Copy link
Copy Markdown

Stacked on #63. The first two commits here are that PR; only the last two are new. It won't compile without #63, because the scrollEdgeEffectHidden rename lives there. Merge #63 first and this rebases cleanly — or close this if the tradeoff below isn't one you want.

The one thing to decide first

This raises the minimum macOS from 13 to 14. That is a product call, not a technical one, so it's entirely yours — everything else here follows from it, and I've kept it in separate commits so you can take the parts you want.

Worth knowing: the current setting is already inconsistent. MACOSX_DEPLOYMENT_TARGET is 14.4 in both project-level configs but 13.0 in both target-level ones, and target-level wins — so the effective minimum today is 13.0, while the project level advertises 14.4. Whichever way you go, those four values should agree.

What raising it buys

Two availability shims become dead code and are deleted outright — 90 lines, including a whole NSViewRepresentable keyboard fallback:

  • View+OnKeyPress.swift → call onKeyPress directly
  • View+FocusEffectDisabled.swift → call focusEffectDisabled directly

The deprecated single-parameter onChange(of:perform:) at ShortcutButton.swift:25 and GeneralTab.swift:203 moves to the two-parameter form, which needs macOS 14.

Two overrides declared in extensions

These are worth a look regardless of the baseline question. Overriding a method from an extension of an Objective-C class is undefined behaviour in Swift — if more than one such override exists, which one wins is not defined.

  • BrowserinoWindow.swift:51 forced canBecomeKey to true on every NSPanel in the process, which includes the open and save panels behind fileImporter. Moved onto BrowserinoWindow, the only panel that actually needs it (its .nonactivatingPanel style is what refuses key status).
  • PreferencesView.swift:12 overrode NSTableView.viewDidMoveToWindow app-wide to clear List backgrounds. Replaced with .scrollContentBackground(.hidden) on the four Lists it existed to serve — the sanctioned API, available since macOS 13.

Swift 6 language mode

SWIFT_VERSION 5.0 → 6.0. AppDelegate becomes @MainActor, which resolves 15 of 16 isolation warnings. The sixteenth is observeValue, deliberately left nonisolated with a hop to the main actor inside: defaults written by another process deliver KVO off the main thread, so MainActor.assumeIsolated would trap there. That isn't hypothetical — it fires if you edit Browserino's rules with defaults write while the app is running.

Three further fixes that only appear in a full compile, never in -typecheck:

  • BrowserUtil's static @AppStorage properties are nonisolated global mutable state (a hard error in Swift 6). The type is only ever used from the main actor, so it's isolated there.
  • SettingsDocument held [String: Any] while FileDocument requires Sendable. It now serializes on the way in and carries Data; the exporter wrote the same pretty-printed JSON either way.
  • setDefaultApplication's completion handler is a @Sendable closure and cannot touch @State or call a main-actor method, so it hops before updating isDefault.

Verification

Compiles with 0 errors and 0 warnings in Swift 6 mode against the macOS 26.5 SDK, targeting macOS 14.0, and the resulting app has been running as my default browser on macOS 27.

Not verified, and worth your eyes: the List backgrounds under scrollContentBackground(.hidden), shortcut recording via the direct onKeyPress, and the menu-bar toggle, which is now one main-actor hop later than before. I had no way to screenshot the running UI on this machine.

LastUpgradeCheck is deliberately untouched — Xcode rewrites it itself when you accept its recommended-settings prompt, and setting it by hand without Xcode validating those settings would be a lie in the file.

Disclosure

Written with AI assistance (Claude Code), same as #63. Everything above was compiled and run before submitting. This one is much more opinionated than #63, so treat it as a proposal rather than a fix — no hard feelings if the baseline bump isn't where you want to go.

matijazezelj and others added 4 commits September 11, 2026 20:31
Bundle(url:) returns nil once an app is moved, renamed or uninstalled, so
force-unwrapping it in RuleItem.body trapped the moment the Rules tab laid
out its List — making a stale rule impossible to reach and delete.

Rows for unresolved apps now render in a degraded state instead of being
force-unwrapped or hidden, so they can still be edited or removed. Apply the
same treatment to CFBundleName and bundleIdentifier, which are both optional
in practice, and to the equivalent sites in the Apps tab, Browsers tab and
the picker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWQa3xpkwVRtrtVh8VkQQo
The symbol survives in SwiftUI.tbd but is absent from the swiftinterface of
both the macOS 26.5 and 27.0 SDKs, so the call no longer compiles against any
current SDK. scrollEdgeEffectHidden(_:for:) carries the same
@available(macOS 26.0, *) and slots straight into the existing guard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWQa3xpkwVRtrtVh8VkQQo
Unify MACOSX_DEPLOYMENT_TARGET on 14.0 — it read 14.4 at project level but
13.0 at target level, so the effective minimum was silently 13.0 — and move
the target to Swift 6 language mode.

With 13.0 gone the two availability shims are dead weight: call onKeyPress
and focusEffectDisabled directly and delete View+OnKeyPress.swift and
View+FocusEffectDisabled.swift. Adopt the two-parameter onChange.

Replace two overrides declared in extensions of ObjC classes, which is
undefined behaviour in Swift and picks a winner arbitrarily when more than
one exists. canBecomeKey belongs on BrowserinoWindow, the only panel that
needs it, rather than on every NSPanel including the open and save panels.
The NSTableView background hack becomes scrollContentBackground(.hidden) on
the four Lists it was there to serve.

AppDelegate is @mainactor; observeValue stays nonisolated because defaults
written by another process arrive off the main thread.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWQa3xpkwVRtrtVh8VkQQo
Errors and warnings that only surface in a full compile, not in -typecheck.

BrowserUtil's static @AppStorage properties are nonisolated global mutable
state; the type is only ever touched from the main actor, so isolate it there.

SettingsDocument holds [String: Any] while FileDocument requires Sendable.
Serialize on the way in and carry Data instead — the exporter wrote the same
pretty-printed JSON either way. setDefaultApplication's completion handler is
a Sendable closure and cannot touch @State or call a main-actor method, so
hop before updating isDefault.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWQa3xpkwVRtrtVh8VkQQo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant