Skip to content

feat(app): support macOS 13 Ventura - #2874

Merged
datlechin merged 6 commits into
mainfrom
feat/macos-13-ventura
Sep 16, 2026
Merged

datlechin merged 6 commits into
mainfrom
feat/macos-13-ventura

Conversation

@datlechin

Copy link
Copy Markdown
Member

Lowers the deployment target from macOS 14.0 to 13.0.

Why it could not move before

Observation is macOS 14 and Apple ships no back-deploy library for it. The toolchain's swift-5.5/macosx directory holds exactly one dylib, libswift_Concurrency.dylib; there is no libswiftObservation. So @Observable cannot run on 13 at all, and 133 classes used it.

A first build at 13.0 reported 982 availability errors. 968 of them were Observation.

What changed

  • 133 @Observable classes to ObservableObject + @Published. @ObservationIgnored properties stay unpublished, and a property written on every layout pass that no view reads stays unpublished too, or the strip re-renders through every window resize.
  • 102 @Bindable to @ObservedObject / @StateObject, plus 83 holders that had no wrapper at all. Those are the dangerous ones: under @Observable a plain let x: T in a view still tracked, under ObservableObject it does not, and it compiles either way.
  • 10 withObservationTracking sites to Combine sinks. Two semantics change: the sink wakes for any published property rather than the one the closure read, so the hot sites compare values first; and objectWillChange fires before the value lands, so delivery hops a run-loop turn.
  • ~190 onChange closures to the single-value form. Four sites genuinely used the old value and get onValueChange, which keeps the previous value in @State.

macOS 14 APIs are replaced or gated:

API On macOS 13
ContentUnavailableView (90 sites) UnavailableStateView, same three initialisers
scrollPosition(id:anchor:) bottom sentinel measured against the viewport through a PreferenceKey
DisclosureTableRow groups render flat; @TableRowBuilder rejects an availability check, so the whole Table branches and the columns are shared
onKeyPress absent; every action has a pointer equivalent in the same surface
TipKit absent
ProxyConfiguration SOCKS refuses with a reason and points at SSH tunnels
NSMenu.palette a plain menu of the same colours with swatches
CADisplayLink transaction completion plus one hop, an approximation for a measurement
backgroundProminence, accessoryBarAction, alternatingRowBackgrounds, inspectorTrackingSeparator, NSTextInsertionIndicator, symbolEffect, quaternarySystemFill, AccessibilityNotification, NSApp.activate(), loadViewIfNeeded() gated, each with its fallback

The OpenSSL dylibs are rebuilt at 13.0. They load at dyld time, so a 14.0 minimum there refuses to start on Ventura however correct the app binary is. Every Mach-O in the bundle is now at or below 13.0. The static archives keep their own minimum on purpose: they link statically, so it is metadata, not a load-time contract.

What is verified, and what is not

Verified here: build clean, swiftlint --strict clean, 19,802 unit tests pass, no Mach-O in the bundle above 13.0, and the app launches and renders.

Not verified: the app has never run on Ventura. This machine is macOS 27 and GitHub retired the macos-13 runner in December 2025, so nothing in CI can execute it either. Running it on 14+ exercises the #available true branches, not the fallbacks.

Also worth a reviewer's attention: of roughly 317 consumer sites in this conversion only about 90 are ones the compiler can check. The rest build clean and fail at runtime. A six-way audit with adversarial verification found 69 such sites and they are fixed here, but a green build proves nothing about that class of defect. QueryInsightsView.isUnlocked is the shape to look for: it calls LicenseManager.shared.isFeatureAvailable(_:), which reads status and currentTier, so it needed a wrapper even though the call site has parentheses.

Test failures on this machine

8 unit tests (AWSSSOFetchTests, SSEEventStreamTests) and 5 UI tests (BackgroundLaunchDockPresenceUITests, ColumnJumpUITests) fail here. All of them fail identically on unmodified main, so they are a local baseline, not a regression. 11 tests that assert macOS 14 API behaviour are gated; ChatTurnObservationTests is rewritten against objectWillChange because it asserted per-property granularity that no longer exists.

Observation is macOS 14 and has no back-deploy library, so the deployment
target could not move while the app used it. Every `@Observable` class
becomes `ObservableObject` with `@Published`, every `@Bindable` becomes
`@ObservedObject` or `@StateObject`, and the ten `withObservationTracking`
sites become Combine sinks that hop a run-loop turn, because
`objectWillChange` fires before the value lands.

The macOS 14 APIs are either replaced or gated. `ContentUnavailableView`
becomes `UnavailableStateView` with the same three initialisers.
`DisclosureTableRow` forces the whole `Table` to branch, because
`@TableRowBuilder` rejects an availability check inside it. TipKit, SOCKS
proxying and `onKeyPress` are absent on 13; each site says what is lost
and why the surface still works without it.

The OpenSSL dylibs are rebuilt at 13.0. They load at dyld time, so a
14.0 minimum there would have refused to start on Ventura even though
the app binary was correct. The static archives keep their own minimum:
they link statically, so it is metadata rather than a load-time contract.
@datlechin

Copy link
Copy Markdown
Member Author

One thing this PR does not solve, and it has to be handled before release

The plugin registry has no way to say what macOS a binary needs. RegistryBinary carries architecture, downloadURL, sha256 and pluginKitVersion and nothing else, and resolvedBinary(for:currentKitVersion:minimumKitVersion:) filters on architecture and kit version alone.

So once the app runs on Ventura, a Ventura user browsing the registry resolves a binary that was built at a 14.0 deployment target, downloads it, and Bundle.loadAndReturnError refuses it. That is the same "Bundle failed to load executable" failure the ABI notes in CLAUDE.md describe, arriving through a different door, and the user has no way to tell what went wrong.

Two ways out:

  1. Re-release every registry plugin at 13.0 before or with the app release, the same ordering the post-ABI-bump checklist already requires. Plugins built from main after this merge pick up 13.0 from project.yml automatically, so this is release-all-plugins.sh, not new code. The gap is that --keep-kit-versions 3 keeps the older 14.0 binaries published, and a Ventura user can still resolve one of those if its kit version is in range.
  2. Give RegistryBinary a minimum-OS field and filter on it in resolvedBinary, with a missing field read as 14.0 so existing entries stay correct. This closes the gap in (1) permanently and makes the registry able to serve both floors at once.

(1) alone leaves a hole; (2) is the durable answer but changes the manifest schema and update-registry.py, which is a decision for whoever owns the registry rather than something to slip into this PR.

Worth recording: docs/changelog.mdx shows the minimum went from 13.5 to 14.0 in an earlier release, so this is a return rather than new ground.

@mintlify

mintlify Bot commented Sep 15, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
TablePro 🟢 Ready View Preview Sep 15, 2026, 9:11 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

# Conflicts:
#	CHANGELOG.md
#	Packages/TableProEditor/Sources/TableProEditorKit/Controller/TextViewController+Lifecycle.swift
#	Packages/TableProEditor/Sources/TableProEditorKit/Find/FindViewController.swift
#	TablePro/Core/Compare/CompareSyncSession.swift
#	TablePro/Core/Services/Infrastructure/UpdaterBridge.swift
#	TablePro/Core/Sync/SyncCoordinator.swift
#	TablePro/Models/UI/MultiRowEditState.swift
#	TablePro/ViewModels/WelcomeViewModel.swift
#	TablePro/Views/Compare/CompareDataPlansView.swift
#	TablePro/Views/Compare/CompareOptionsView.swift
#	TablePro/Views/Components/MagnifiableCanvasView.swift
#	TablePro/Views/ConnectionForm/ViewModels/AuthPaneViewModel.swift
#	TablePro/Views/QueryPlan/QueryPlanDiagramView.swift
#	TablePro/Views/QueryPlan/QueryPlanResultView.swift
#	TablePro/Views/Settings/GeneralSettingsView.swift
#	TablePro/Views/Toolbar/ConnectionSwitcherPopover.swift
#	TablePro/Views/Welcome/WelcomeActionsPanel.swift
#	TablePro/Views/Welcome/WelcomeConnectionList.swift
#	TablePro/Views/Welcome/WelcomeConnectionRow.swift
#	TablePro/Views/Welcome/WelcomeConnectionsPanel.swift
#	TablePro/Views/Welcome/WelcomeContextMenus.swift
#	TablePro/Views/Welcome/WelcomeGroupRows.swift
#	TablePro/Views/Welcome/WelcomePresentations.swift
#	TablePro/Views/Welcome/WelcomeWindowView.swift
# Conflicts:
#	CHANGELOG.md
#	CLAUDE.md
#	TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift
#	TablePro/Views/Editor/QueryEditorView.swift
#	TablePro/Views/Main/Child/MainEditorContentView.swift
#	docs/development/setup.mdx
#	docs/features/overview.mdx
#	docs/index.mdx
#	docs/installation.mdx
#	project.yml
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