mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
Keep dropped-macOS updaters on their last compatible build and explain why (#38777)
* Keep dropped-macOS updaters on their last compatible build and explain why Stamp minimumSystemVersion into every mac update manifest from the built app's LSMinimumSystemVersion, and refuse to publish an ungated release. When the updater withholds a newer release for this macOS, show a one-time notice and say so in Settings and the update card. * Trim the unsupported-Mac notice copy
This commit is contained in:
@@ -401,7 +401,9 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.workflow_sha }}
|
||||
path: .workflow-tools
|
||||
sparse-checkout: config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs
|
||||
sparse-checkout: |
|
||||
config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs
|
||||
config/scripts/desktop-distribution/macos/gate-mac-update-manifests.mjs
|
||||
sparse-checkout-cone-mode: false
|
||||
persist-credentials: false
|
||||
|
||||
@@ -440,7 +442,11 @@ jobs:
|
||||
env:
|
||||
MAC_ARCH: ${{ matrix.arch }}
|
||||
MAC_VERSION: ${{ needs.orcad-template-support.outputs.version }}
|
||||
run: node .workflow-tools/config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs stage "$MAC_ARCH" "$MAC_VERSION" dist .build/mac-manifests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Builder cannot write this field, so older macOS updaters would install a build that won't launch.
|
||||
node .workflow-tools/config/scripts/desktop-distribution/macos/gate-mac-update-manifests.mjs dist
|
||||
node .workflow-tools/config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs stage "$MAC_ARCH" "$MAC_VERSION" dist .build/mac-manifests
|
||||
|
||||
- name: Mint adhoc repo token
|
||||
id: app_token
|
||||
|
||||
@@ -330,6 +330,18 @@ jobs:
|
||||
owner: stablyai
|
||||
repositories: orca-daily
|
||||
|
||||
# Builder cannot write this field, so older macOS updaters would install a build that won't launch.
|
||||
- name: Gate the update manifest on the minimum macOS
|
||||
if: steps.freshness.outputs.should_build == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app_token_final.outputs.token }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node config/scripts/desktop-distribution/macos/gate-mac-update-manifests.mjs dist
|
||||
gh release upload "$TAG" dist/*-mac.yml --repo "$DAILY_REPO" --clobber
|
||||
|
||||
# Verify latest-mac.yml before exposing its release to prevent picker downloads returning 404.
|
||||
- name: Verify update manifest published
|
||||
if: steps.freshness.outputs.should_build == 'true'
|
||||
|
||||
@@ -298,6 +298,17 @@ jobs:
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
# Builder cannot write this field, so older macOS updaters would install a build that won't launch.
|
||||
- name: Gate the update manifest on the minimum macOS
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app_token_publish.outputs.token }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node config/scripts/desktop-distribution/macos/gate-mac-update-manifests.mjs dist
|
||||
gh release upload "$TAG" dist/*-mac.yml --repo "$HOURLY_REPO" --clobber
|
||||
|
||||
# Verify latest-mac.yml before exposing release; missing manifests cause picker download 404s.
|
||||
- name: Verify update manifest published
|
||||
shell: bash
|
||||
|
||||
@@ -240,6 +240,22 @@ jobs:
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
# Builder cannot write this field, so older macOS updaters would install a build that won't launch.
|
||||
- name: Gate the update manifest on the minimum macOS
|
||||
# Tags cut before the gate keep their manifest unchanged.
|
||||
if: hashFiles('config/scripts/desktop-distribution/macos/gate-mac-update-manifests.mjs') != ''
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ inputs.tag }}
|
||||
PUBLISH: ${{ !inputs.qualification_only }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node config/scripts/desktop-distribution/macos/gate-mac-update-manifests.mjs dist
|
||||
if [ "$PUBLISH" = true ]; then
|
||||
gh release upload "$TAG" dist/*-mac.yml --repo "$GITHUB_REPOSITORY" --clobber
|
||||
fi
|
||||
|
||||
- name: Verify release remains draft after artifact upload
|
||||
if: ${{ !inputs.qualification_only }}
|
||||
# Re-draft premature builder publication before failing; latest must not keep an incomplete Windows release.
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
// Stamps each macOS update manifest with the app's minimum macOS so the updater on an older
|
||||
// macOS stays on its last compatible build instead of installing one that will not launch.
|
||||
// Usage: node gate-mac-update-manifests.mjs <dist-dir>
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
// electron-updater compares `minimumSystemVersion` with os.release(), the Darwin version.
|
||||
export function darwinVersionForMacos(macosVersion) {
|
||||
const major = Number.parseInt(String(macosVersion).split('.')[0] ?? '', 10)
|
||||
if (major >= 11 && major <= 15) {
|
||||
return `${major + 9}.0.0`
|
||||
}
|
||||
if (major >= 26) {
|
||||
return `${major - 1}.0.0`
|
||||
}
|
||||
throw new Error(`Unsupported LSMinimumSystemVersion: ${macosVersion}`)
|
||||
}
|
||||
|
||||
export function withMinimumSystemVersion(manifest, darwinVersion) {
|
||||
const lines = manifest
|
||||
.replace(/\n+$/, '')
|
||||
.split('\n')
|
||||
.filter((line) => !line.startsWith('minimumSystemVersion:'))
|
||||
lines.push(`minimumSystemVersion: ${darwinVersion}`)
|
||||
return `${lines.join('\n')}\n`
|
||||
}
|
||||
|
||||
function readMinimumMacosVersion(distDir) {
|
||||
const versions = new Set()
|
||||
for (const entry of readdirSync(distDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory() || !entry.name.startsWith('mac')) {
|
||||
continue
|
||||
}
|
||||
for (const app of readdirSync(join(distDir, entry.name))) {
|
||||
if (!app.endsWith('.app')) {
|
||||
continue
|
||||
}
|
||||
const plist = join(distDir, entry.name, app, 'Contents', 'Info.plist')
|
||||
versions.add(
|
||||
execFileSync('plutil', ['-extract', 'LSMinimumSystemVersion', 'raw', plist], {
|
||||
encoding: 'utf8'
|
||||
}).trim()
|
||||
)
|
||||
}
|
||||
}
|
||||
if (versions.size !== 1) {
|
||||
throw new Error(
|
||||
`Expected one LSMinimumSystemVersion across ${distDir}, found ${[...versions].join(', ')}`
|
||||
)
|
||||
}
|
||||
return [...versions][0]
|
||||
}
|
||||
|
||||
function main(distDir) {
|
||||
const macosVersion = readMinimumMacosVersion(distDir)
|
||||
const darwinVersion = darwinVersionForMacos(macosVersion)
|
||||
const manifests = readdirSync(distDir).filter((name) => name.endsWith('-mac.yml'))
|
||||
if (manifests.length === 0) {
|
||||
throw new Error(`No *-mac.yml manifest in ${distDir}`)
|
||||
}
|
||||
for (const name of manifests) {
|
||||
const path = join(distDir, name)
|
||||
writeFileSync(path, withMinimumSystemVersion(readFileSync(path, 'utf8'), darwinVersion))
|
||||
console.log(`${name}: minimumSystemVersion ${darwinVersion} (macOS ${macosVersion})`)
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] && resolve(process.argv[1]) === resolve(import.meta.filename)) {
|
||||
if (!process.argv[2]) {
|
||||
console.error('Usage: node gate-mac-update-manifests.mjs <dist-dir>')
|
||||
process.exit(1)
|
||||
}
|
||||
main(process.argv[2])
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { darwinVersionForMacos, withMinimumSystemVersion } from './gate-mac-update-manifests.mjs'
|
||||
|
||||
describe('darwinVersionForMacos', () => {
|
||||
it('maps macOS releases to the Darwin version os.release() reports', () => {
|
||||
expect(darwinVersionForMacos('12.0')).toBe('21.0.0')
|
||||
expect(darwinVersionForMacos('13.0')).toBe('22.0.0')
|
||||
expect(darwinVersionForMacos('15.4')).toBe('24.0.0')
|
||||
expect(darwinVersionForMacos('26.0')).toBe('25.0.0')
|
||||
expect(() => darwinVersionForMacos('10.15')).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('withMinimumSystemVersion', () => {
|
||||
it('replaces any earlier gate with exactly one top-level entry', () => {
|
||||
const manifest =
|
||||
'version: 1.5.0\nfiles:\n - url: Orca-1.5.0-mac.zip\nminimumSystemVersion: 21.0.0\n'
|
||||
expect(withMinimumSystemVersion(manifest, '22.0.0')).toBe(
|
||||
'version: 1.5.0\nfiles:\n - url: Orca-1.5.0-mac.zip\nminimumSystemVersion: 22.0.0\n'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -141,7 +141,11 @@ describe('publishCompleteDraftReleases', () => {
|
||||
.filter(({ name }) => name.startsWith('latest'))
|
||||
.map(({ id, name }) => [
|
||||
`https://api.github.com/repos/stablyai/orca/releases/assets/${id}`,
|
||||
name === 'latest.yml' ? 'path: orca-windows-setup.exe\n' : ''
|
||||
name === 'latest.yml'
|
||||
? 'path: orca-windows-setup.exe\n'
|
||||
: name === 'latest-mac.yml'
|
||||
? 'minimumSystemVersion: 22.0.0\n'
|
||||
: ''
|
||||
])
|
||||
)
|
||||
)
|
||||
|
||||
@@ -103,6 +103,10 @@ export async function verifyRequiredReleaseAssets({ repo, tag, token }) {
|
||||
continue
|
||||
}
|
||||
const manifestText = await fetchAssetText(repo, manifestAsset, token)
|
||||
// Without it, updaters on a dropped macOS install a build that cannot launch.
|
||||
if (manifestName === 'latest-mac.yml' && !/^minimumSystemVersion:/m.test(manifestText)) {
|
||||
throw new Error(`Release ${tag} latest-mac.yml has no minimumSystemVersion gate.`)
|
||||
}
|
||||
for (const referencedName of extractManifestAssetNames(manifestText)) {
|
||||
requiredNames.add(referencedName)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import { registerMacKeyboardLayoutChangeNotifications } from './macos-keyboard-l
|
||||
import { isProfileStateSaveDelayed } from '../startup/profile-state-save-delay'
|
||||
import { readCommandStdout, readKeyboardInputSourceId } from './macos-keyboard-input-source'
|
||||
import { getWindowsAdministratorStatus } from '../windows/windows-administrator-status'
|
||||
import { getMacosUpgradeSupport } from '../macos-update-requirement/macos-upgrade-support'
|
||||
|
||||
import type { ReconnectFactsFor } from '../persistence/window-save-view'
|
||||
|
||||
@@ -148,6 +149,9 @@ export function registerAppHandlers(store: Store, options: RegisterAppHandlersOp
|
||||
return detection
|
||||
})
|
||||
ipcMain.handle('app:getWindowsAdministratorStatus', getWindowsAdministratorStatus)
|
||||
ipcMain.handle('app:getMacosUpgradeSupport', (_event, minimumMacosVersion: unknown) =>
|
||||
getMacosUpgradeSupport(minimumMacosVersion)
|
||||
)
|
||||
|
||||
ipcMain.handle('app:getFeatureWallAssetBaseUrl', (): string => getFeatureWallAssetBaseUrl())
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { app } from 'electron'
|
||||
import { runProcess } from '@orca/process-host'
|
||||
import type { MacosUpgradeSupport } from '../../shared/macos-upgrade-support'
|
||||
|
||||
// Ventura's Intel model boundaries: https://support.apple.com/en-us/102861.
|
||||
const VENTURA_INTEL_MODELS = {
|
||||
MacBook: [10, 10],
|
||||
MacBookAir: [8, 9],
|
||||
MacBookPro: [14, 16],
|
||||
Macmini: [8, 8],
|
||||
iMac: [18, 20],
|
||||
iMacPro: [1, 1],
|
||||
MacPro: [7, 7]
|
||||
} as const
|
||||
|
||||
export function getIntelVenturaSupport(modelIdentifier: string): MacosUpgradeSupport {
|
||||
const match =
|
||||
/^(MacBookAir|MacBookPro|MacBook|Macmini|iMacPro|iMac|MacPro)([1-9]\d*),[1-9]\d*$/u.exec(
|
||||
modelIdentifier
|
||||
)
|
||||
const limits = match
|
||||
? Object.entries(VENTURA_INTEL_MODELS).find(([name]) => name === match[1])?.[1]
|
||||
: undefined
|
||||
if (!match || !limits) {
|
||||
return 'unknown'
|
||||
}
|
||||
const major = Number(match[2])
|
||||
if (major < limits[0]) {
|
||||
return 'unsupported'
|
||||
}
|
||||
return major <= limits[1] ? 'supported' : 'unknown'
|
||||
}
|
||||
|
||||
let intelVenturaSupport: Promise<MacosUpgradeSupport> | undefined
|
||||
|
||||
async function readIntelVenturaSupport(): Promise<MacosUpgradeSupport> {
|
||||
const result = await runProcess({
|
||||
program: '/usr/sbin/sysctl',
|
||||
args: ['-n', 'hw.model'],
|
||||
timeoutMs: 1_000,
|
||||
maxOutputBytes: 128
|
||||
})
|
||||
const model =
|
||||
result.code === 0 && !result.timedOut && !result.outputTruncated ? result.stdout.trim() : ''
|
||||
return model ? getIntelVenturaSupport(model) : 'unknown'
|
||||
}
|
||||
|
||||
/** Whether Apple supports `minimumMacosVersion` on this Mac; only macOS 13 has model data. */
|
||||
export function getMacosUpgradeSupport(minimumMacosVersion: unknown): Promise<MacosUpgradeSupport> {
|
||||
if (process.platform !== 'darwin') {
|
||||
return Promise.resolve('unknown')
|
||||
}
|
||||
// Every Apple Silicon Mac supports macOS 13 through 26.
|
||||
if (process.arch === 'arm64' || app.runningUnderARM64Translation) {
|
||||
return Promise.resolve('supported')
|
||||
}
|
||||
if (minimumMacosVersion !== '13') {
|
||||
return Promise.resolve('unknown')
|
||||
}
|
||||
return (intelVenturaSupport ??= readIntelVenturaSupport().catch(() => 'unknown'))
|
||||
}
|
||||
@@ -25,7 +25,10 @@ export function statusesEqual(left: UpdateStatus, right: UpdateStatus): boolean
|
||||
left.delayedUpdate?.version === right.delayedUpdate?.version &&
|
||||
left.delayedUpdate?.availableAt === right.delayedUpdate?.availableAt &&
|
||||
left.delayedUpdate?.releasedAt === right.delayedUpdate?.releasedAt &&
|
||||
left.delayedUpdate?.delayDays === right.delayedUpdate?.delayDays
|
||||
left.delayedUpdate?.delayDays === right.delayedUpdate?.delayDays &&
|
||||
left.unsupportedSystemUpdate?.version === right.unsupportedSystemUpdate?.version &&
|
||||
left.unsupportedSystemUpdate?.minimumMacosVersion ===
|
||||
right.unsupportedSystemUpdate?.minimumMacosVersion
|
||||
)
|
||||
case 'available':
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getUnsupportedSystemUpdate } from './updater-system-requirement'
|
||||
|
||||
describe('getUnsupportedSystemUpdate', () => {
|
||||
const gated = { version: '1.5.0', minimumSystemVersion: '22.0.0' }
|
||||
|
||||
it('reports a newer release gated above this macOS', () => {
|
||||
expect(getUnsupportedSystemUpdate(gated, '1.4.9', 'darwin', '21.6.0')).toEqual({
|
||||
version: '1.5.0',
|
||||
minimumMacosVersion: '13'
|
||||
})
|
||||
expect(
|
||||
getUnsupportedSystemUpdate(
|
||||
{ version: '2.0.0', minimumSystemVersion: '25.0.0' },
|
||||
'1.4.9',
|
||||
'darwin',
|
||||
'24.6.0'
|
||||
)
|
||||
).toEqual({ version: '2.0.0', minimumMacosVersion: '26' })
|
||||
})
|
||||
|
||||
it('ignores releases this Mac can install or that are not newer', () => {
|
||||
expect(getUnsupportedSystemUpdate(gated, '1.4.9', 'darwin', '22.0.0')).toBeUndefined()
|
||||
expect(getUnsupportedSystemUpdate(gated, '1.5.0', 'darwin', '21.6.0')).toBeUndefined()
|
||||
expect(getUnsupportedSystemUpdate(gated, '1.4.9', 'win32', '10.0.22631')).toBeUndefined()
|
||||
expect(
|
||||
getUnsupportedSystemUpdate({ version: '1.5.0' }, '1.4.9', 'darwin', '21.6.0')
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
import { release } from 'node:os'
|
||||
import type { UpdateInfo } from 'electron-updater'
|
||||
import type { UnsupportedSystemUpdate } from '../../../shared/update-status-types'
|
||||
import { compareVersions } from './updater-fallback'
|
||||
|
||||
function darwinParts(version: string): number[] | null {
|
||||
const parts = version.split('.').map((part) => Number.parseInt(part, 10))
|
||||
return parts.every(Number.isInteger) ? parts : null
|
||||
}
|
||||
|
||||
function isDarwinOlder(current: number[], minimum: number[]): boolean {
|
||||
for (let index = 0; index < minimum.length; index += 1) {
|
||||
const difference = (current[index] ?? 0) - (minimum[index] ?? 0)
|
||||
if (difference !== 0) {
|
||||
return difference < 0
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// macOS 11-15 run Darwin 20-24; macOS 26 runs Darwin 25.
|
||||
function macosMajorForDarwinMajor(darwinMajor: number): number | null {
|
||||
if (darwinMajor < 20) {
|
||||
return null
|
||||
}
|
||||
return darwinMajor <= 24 ? darwinMajor - 9 : darwinMajor + 1
|
||||
}
|
||||
|
||||
/** The newer release electron-updater withheld because this Mac's macOS is below its manifest gate. */
|
||||
export function getUnsupportedSystemUpdate(
|
||||
info: Pick<UpdateInfo, 'version' | 'minimumSystemVersion'> | null | undefined,
|
||||
currentVersion: string,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
darwinVersion: string = release()
|
||||
): UnsupportedSystemUpdate | undefined {
|
||||
if (platform !== 'darwin' || !info?.minimumSystemVersion) {
|
||||
return undefined
|
||||
}
|
||||
if (compareVersions(info.version, currentVersion) <= 0) {
|
||||
return undefined
|
||||
}
|
||||
const minimum = darwinParts(info.minimumSystemVersion)
|
||||
const current = darwinParts(darwinVersion)
|
||||
if (!minimum || !current || !isDarwinOlder(current, minimum)) {
|
||||
return undefined
|
||||
}
|
||||
const macosMajor = macosMajorForDarwinMajor(minimum[0])
|
||||
return macosMajor === null
|
||||
? undefined
|
||||
: { version: info.version, minimumMacosVersion: String(macosMajor) }
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from './mac-install/updater-mac-install'
|
||||
import { compareVersions } from './releases/updater-fallback'
|
||||
import { fetchChangelog } from './releases/updater-changelog'
|
||||
import { getUnsupportedSystemUpdate } from './releases/updater-system-requirement'
|
||||
import type { UpdaterHandlerContext } from './updater-handler-context'
|
||||
import { recordUpdaterLifecycle } from './updater-lifecycle-diagnostics'
|
||||
import {
|
||||
@@ -171,7 +172,7 @@ export function registerAutoUpdaterHandlers({
|
||||
})()
|
||||
})
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
autoUpdater.on('update-not-available', (info) => {
|
||||
if (getActiveUpdateCheckEventAttemptId() === null) {
|
||||
return
|
||||
}
|
||||
@@ -197,9 +198,14 @@ export function registerAutoUpdaterHandlers({
|
||||
}
|
||||
}
|
||||
}
|
||||
const unsupportedSystemUpdate = getUnsupportedSystemUpdate(info, app.getVersion())
|
||||
// Preserve manual-install recovery when a recheck finds no newer release.
|
||||
sendStatus(
|
||||
retainedStatus ?? { state: 'not-available', userInitiated: wasUserInitiated || undefined }
|
||||
retainedStatus ?? {
|
||||
state: 'not-available',
|
||||
userInitiated: wasUserInitiated || undefined,
|
||||
...(unsupportedSystemUpdate ? { unsupportedSystemUpdate } : {})
|
||||
}
|
||||
)
|
||||
if (localBuildCheck || pinnedBuildCheck) {
|
||||
restoreReleaseUpdateSource()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { UiLanguageDetection } from '../../shared/ui-locale'
|
||||
import type { WindowMaterial } from '../../shared/window-material'
|
||||
import type { AppIdentity } from '../../shared/app-identity'
|
||||
import type { MacosUpgradeSupport } from '../../shared/macos-upgrade-support'
|
||||
import type { E2EConfig } from '../../shared/e2e-config'
|
||||
import type { ExecutionHostId } from '../../shared/execution-host'
|
||||
import type {
|
||||
@@ -24,6 +25,8 @@ export type AppApi = {
|
||||
getIdentity: () => Promise<AppIdentity>
|
||||
/** Local Windows desktop elevation; null when unavailable. Does not describe remote hosts. */
|
||||
getWindowsAdministratorStatus: () => Promise<boolean | null>
|
||||
/** Local Mac only; optional for older clients. */
|
||||
getMacosUpgradeSupport?: (minimumMacosVersion: string) => Promise<MacosUpgradeSupport>
|
||||
/** Restarts the installed Windows desktop with administrator approval and replaces its local terminal host. */
|
||||
restartAsAdministrator: () => Promise<void>
|
||||
/** Append filenames to this base: Vite /@fs in dev, file:// resources when packaged. */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { AppIdentity } from '../../shared/app-identity'
|
||||
import type { MacosUpgradeSupport } from '../../shared/macos-upgrade-support'
|
||||
import type { FloatingTerminalCwdRequest } from '../../shared/ui-chrome-types'
|
||||
import type { MacCapturedDigitRowChord } from '../../shared/macos-symbolic-hotkeys'
|
||||
import type { MarkdownDocument } from '../../shared/filesystem-entry-types'
|
||||
@@ -26,6 +27,8 @@ export const appApi = {
|
||||
getIdentity: (): Promise<AppIdentity> => ipcRenderer.invoke('app:getIdentity'),
|
||||
getWindowsAdministratorStatus: (): Promise<boolean | null> =>
|
||||
ipcRenderer.invoke('app:getWindowsAdministratorStatus'),
|
||||
getMacosUpgradeSupport: (minimumMacosVersion: string): Promise<MacosUpgradeSupport> =>
|
||||
ipcRenderer.invoke('app:getMacosUpgradeSupport', minimumMacosVersion),
|
||||
restartAsAdministrator: (): Promise<void> =>
|
||||
prepareAndInvokeAppRestart(
|
||||
window,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { StarNagToastHost } from '../components/star-nag/StarNagToastHost'
|
||||
import { TelemetryFirstLaunchSurface } from '../components/TelemetryFirstLaunchSurface'
|
||||
import { ZoomOverlay } from '../components/ZoomOverlay'
|
||||
import { WslMoveDialogHost } from '../components/wsl-move/WslMoveDialog'
|
||||
import { MacosUpdateRequirementHost } from '../components/macos-update-requirement/MacosUpdateRequirementHost'
|
||||
import { shouldRenderPetOverlay } from '../components/pet/pet-overlay-visibility'
|
||||
import { useAppStore } from '../store'
|
||||
import type { UpdateStatus } from '../../../shared/update-status-types'
|
||||
@@ -296,6 +297,7 @@ export const AppRootSurfaces = memo(function AppRootSurfaces(props: {
|
||||
</OverlayBoundary>
|
||||
</Suspense>
|
||||
) : null}
|
||||
<MacosUpdateRequirementHost />
|
||||
<AppNotificationCards
|
||||
activeView={activeView}
|
||||
shouldMountUpdateCard={shouldMountUpdateCard}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { defineFixture } from '../../component-fixtures/component-fixture'
|
||||
import { MacosUpdateRequirementDialog } from './MacosUpdateRequirementDialog'
|
||||
|
||||
const requirement = { version: '1.5.0', minimumMacosVersion: '13' }
|
||||
|
||||
function fixture(support: 'supported' | 'unsupported' | 'unknown' | undefined) {
|
||||
return defineFixture({
|
||||
render: () => (
|
||||
<MacosUpdateRequirementDialog
|
||||
open
|
||||
onOpenChange={() => {}}
|
||||
requirement={requirement}
|
||||
support={support}
|
||||
onOpenUrl={() => {}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export const canUpgrade = fixture('supported')
|
||||
export const cannotUpgrade = fixture('unsupported')
|
||||
export const unknown = fixture('unknown')
|
||||
export const checking = fixture(undefined)
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
import { useRef, type JSX, type MouseEvent } from 'react'
|
||||
import { ArrowUpRight, ChevronDown } from 'lucide-react'
|
||||
import type { MacosUpgradeSupport } from '../../../../shared/macos-upgrade-support'
|
||||
import type { UnsupportedSystemUpdate } from '../../../../shared/update-status-types'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
const MACOS_UPDATE_GUIDE_URL = 'https://support.apple.com/en-us/108382'
|
||||
const VENTURA_COMPATIBILITY_URL = 'https://support.apple.com/en-us/102861'
|
||||
const MACOS_SUPPORT_DISCUSSION_URL = 'https://github.com/stablyai/orca/discussions/38765'
|
||||
|
||||
export function MacosUpdateRequirementDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
requirement,
|
||||
support,
|
||||
onOpenUrl
|
||||
}: {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
requirement: UnsupportedSystemUpdate
|
||||
/** Undefined while the local probe runs. */
|
||||
support: MacosUpgradeSupport | undefined
|
||||
onOpenUrl: (url: string, event: MouseEvent<HTMLButtonElement>) => void
|
||||
}): JSX.Element {
|
||||
const primaryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const macos = requirement.minimumMacosVersion
|
||||
const checking = support === undefined
|
||||
const canUpgrade = support === 'supported'
|
||||
const cannotUpgrade = support === 'unsupported'
|
||||
const primaryUrl = canUpgrade
|
||||
? MACOS_UPDATE_GUIDE_URL
|
||||
: cannotUpgrade
|
||||
? MACOS_SUPPORT_DISCUSSION_URL
|
||||
: macos === '13'
|
||||
? VENTURA_COMPATIBILITY_URL
|
||||
: MACOS_UPDATE_GUIDE_URL
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
scrollable
|
||||
onOpenAutoFocus={(event) => {
|
||||
if (!checking) {
|
||||
event.preventDefault()
|
||||
primaryButtonRef.current?.focus({ preventScroll: true })
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle size="display">
|
||||
{translate('macosUpdateRequirement.title', 'Orca updates now need macOS {{macos}}', {
|
||||
macos
|
||||
})}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{translate(
|
||||
'macosUpdateRequirement.description',
|
||||
'Orca {{version}} requires macOS {{macos}} or later. This version keeps working, but it won’t get new updates on this Mac.',
|
||||
{ version: requirement.version, macos }
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-2" aria-live="polite" aria-busy={checking}>
|
||||
<p className="text-sm leading-relaxed">
|
||||
{checking
|
||||
? translate(
|
||||
'macosUpdateRequirement.checking',
|
||||
'Checking whether this Mac can upgrade…'
|
||||
)
|
||||
: canUpgrade
|
||||
? translate(
|
||||
'macosUpdateRequirement.upgradeDescription',
|
||||
'Your Mac can upgrade. Update macOS to get the latest Orca.'
|
||||
)
|
||||
: cannotUpgrade
|
||||
? translate(
|
||||
'macosUpdateRequirement.unsupportedDescription',
|
||||
'Apple doesn’t support macOS {{macos}} on this Mac.',
|
||||
{ macos }
|
||||
)
|
||||
: translate(
|
||||
'macosUpdateRequirement.unknownDescription',
|
||||
'We couldn’t confirm an upgrade for this Mac. Apple’s compatibility guide can help.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger variant="row">
|
||||
{translate('macosUpdateRequirement.whyTitle', 'Why is this changing?')}
|
||||
<ChevronDown
|
||||
className="size-4 shrink-0 text-muted-foreground group-data-[state=open]:rotate-180"
|
||||
aria-hidden
|
||||
/>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="space-y-3 pt-2 text-sm leading-relaxed">
|
||||
<p>
|
||||
{translate(
|
||||
'macosUpdateRequirement.whyIntro',
|
||||
'We updated Electron, the foundation Orca runs on, to bring:'
|
||||
)}
|
||||
</p>
|
||||
<ul className="list-disc space-y-2 pl-4">
|
||||
<li>
|
||||
{translate(
|
||||
'macosUpdateRequirement.whySecurity',
|
||||
'Ongoing security fixes from Electron and Chromium.'
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
{translate(
|
||||
'macosUpdateRequirement.whyPerformance',
|
||||
'Faster startup and more efficient native API calls.'
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
{translate(
|
||||
'macosUpdateRequirement.whySystemApis',
|
||||
'Support for current macOS notification APIs and secure Keychain storage.'
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
{translate(
|
||||
'macosUpdateRequirement.whyRequirement',
|
||||
'That newer foundation requires macOS {{macos}} or later.',
|
||||
{ macos }
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
{translate('macosUpdateRequirement.dismiss', 'Got it')}
|
||||
</Button>
|
||||
<Button
|
||||
ref={primaryButtonRef}
|
||||
disabled={checking}
|
||||
onClick={(event) => onOpenUrl(primaryUrl, event)}
|
||||
>
|
||||
{checking
|
||||
? translate('macosUpdateRequirement.checkingAction', 'Checking your Mac…')
|
||||
: canUpgrade
|
||||
? translate('macosUpdateRequirement.updateAction', 'How to update macOS')
|
||||
: cannotUpgrade
|
||||
? translate('macosUpdateRequirement.discussionAction', 'Join the discussion')
|
||||
: translate('macosUpdateRequirement.checkAction', 'Check compatibility')}
|
||||
{!checking ? <ArrowUpRight className="size-4" aria-hidden /> : null}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Suspense, type JSX } from 'react'
|
||||
import { ModalBoundary } from '@/app-shell/app-root-surface-boundaries'
|
||||
import { lazyWithRetry as lazy } from '@/lib/lazy-with-retry'
|
||||
import { isWebClientLocation } from '@/lib/web-client-location'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
const MacosUpdateRequirementNotice = lazy(() => import('./MacosUpdateRequirementNotice'))
|
||||
|
||||
/** Loads the notice only once the updater withholds a release this Mac cannot run. */
|
||||
export function MacosUpdateRequirementHost(): JSX.Element | null {
|
||||
const requirement = useAppStore((s) =>
|
||||
s.updateStatus.state === 'not-available' ? s.updateStatus.unsupportedSystemUpdate : undefined
|
||||
)
|
||||
const seen = useAppStore((s) => s.seenMacosUpdateRequirement)
|
||||
const persistedUIReady = useAppStore((s) => s.persistedUIReady)
|
||||
// A web client's update status describes its host, not the browser's machine.
|
||||
if (
|
||||
!requirement ||
|
||||
!persistedUIReady ||
|
||||
isWebClientLocation() ||
|
||||
requirement.minimumMacosVersion === seen
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<ModalBoundary boundaryId="modal.macos-update-requirement" resetKey>
|
||||
<MacosUpdateRequirementNotice requirement={requirement} />
|
||||
</ModalBoundary>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useEffect, useState, type JSX } from 'react'
|
||||
import type { MacosUpgradeSupport } from '../../../../shared/macos-upgrade-support'
|
||||
import type { UnsupportedSystemUpdate } from '../../../../shared/update-status-types'
|
||||
import { openAppHttpLink } from '@/lib/app-http-link-open'
|
||||
import { useAppStore } from '@/store'
|
||||
import { MacosUpdateRequirementDialog } from './MacosUpdateRequirementDialog'
|
||||
|
||||
/** One-time notice when the updater withholds a release this Mac's macOS cannot run. */
|
||||
export default function MacosUpdateRequirementNotice({
|
||||
requirement
|
||||
}: {
|
||||
requirement: UnsupportedSystemUpdate
|
||||
}): JSX.Element {
|
||||
const markSeen = useAppStore((s) => s.markMacosUpdateRequirementSeen)
|
||||
const [open, setOpen] = useState(true)
|
||||
const [support, setSupport] = useState<MacosUpgradeSupport>()
|
||||
const { minimumMacosVersion } = requirement
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
void Promise.resolve(window.api.app.getMacosUpgradeSupport?.(minimumMacosVersion))
|
||||
.catch(() => undefined)
|
||||
.then((result) => {
|
||||
if (!cancelled) {
|
||||
setSupport(result ?? 'unknown')
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [minimumMacosVersion])
|
||||
|
||||
const handleOpenChange = (next: boolean): void => {
|
||||
setOpen(next)
|
||||
if (!next) {
|
||||
markSeen(minimumMacosVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MacosUpdateRequirementDialog
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
requirement={requirement}
|
||||
support={support}
|
||||
onOpenUrl={(url, event) => {
|
||||
handleOpenChange(false)
|
||||
openAppHttpLink(url, { event })
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -60,10 +60,19 @@ export function UpdateCardStateContent({
|
||||
)
|
||||
}
|
||||
if (status.state === 'not-available') {
|
||||
const unsupported = status.unsupportedSystemUpdate
|
||||
return (
|
||||
<UpdateCheckFeedback
|
||||
icon="check"
|
||||
text={translate('auto.components.UpdateCard.ea2a41adbe', "You're on the latest version.")}
|
||||
text={
|
||||
unsupported
|
||||
? translate(
|
||||
'macosUpdateRequirement.settingsStatus',
|
||||
'Orca {{version}} requires macOS {{macos}} or later. This is the latest version for this Mac.',
|
||||
{ version: unsupported.version, macos: unsupported.minimumMacosVersion }
|
||||
)
|
||||
: translate('auto.components.UpdateCard.ea2a41adbe', "You're on the latest version.")
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -212,25 +212,34 @@ export function GeneralUpdateSettingsSection(): React.JSX.Element {
|
||||
</>
|
||||
)}
|
||||
{updateStatus.state === 'not-available' &&
|
||||
(updateStatus.delayedUpdate
|
||||
? isDelayedUpdatePending(updateStatus.delayedUpdate, now)
|
||||
? translate(
|
||||
'settings.updateDelay.upToDateWithHeld',
|
||||
'You’re up to date. A newer release is on hold until {{date}}.',
|
||||
{
|
||||
date: formatUpdateDelayDate(
|
||||
Date.parse(updateStatus.delayedUpdate.availableAt)
|
||||
)
|
||||
}
|
||||
)
|
||||
(updateStatus.unsupportedSystemUpdate
|
||||
? translate(
|
||||
'macosUpdateRequirement.settingsStatus',
|
||||
'Orca {{version}} requires macOS {{macos}} or later. This is the latest version for this Mac.',
|
||||
{
|
||||
version: updateStatus.unsupportedSystemUpdate.version,
|
||||
macos: updateStatus.unsupportedSystemUpdate.minimumMacosVersion
|
||||
}
|
||||
)
|
||||
: updateStatus.delayedUpdate
|
||||
? isDelayedUpdatePending(updateStatus.delayedUpdate, now)
|
||||
? translate(
|
||||
'settings.updateDelay.upToDateWithHeld',
|
||||
'You’re up to date. A newer release is on hold until {{date}}.',
|
||||
{
|
||||
date: formatUpdateDelayDate(
|
||||
Date.parse(updateStatus.delayedUpdate.availableAt)
|
||||
)
|
||||
}
|
||||
)
|
||||
: translate(
|
||||
'settings.updateDelay.heldReleaseReady',
|
||||
'A newer release is ready. Check for updates to get it.'
|
||||
)
|
||||
: translate(
|
||||
'settings.updateDelay.heldReleaseReady',
|
||||
'A newer release is ready. Check for updates to get it.'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.settings.GeneralUpdateSettingsSection.f40d88390d',
|
||||
'You’re on the latest version.'
|
||||
))}
|
||||
'auto.components.settings.GeneralUpdateSettingsSection.f40d88390d',
|
||||
'You’re on the latest version.'
|
||||
))}
|
||||
{updateStatus.state === 'downloading' &&
|
||||
translate(
|
||||
'auto.components.settings.GeneralUpdateSettingsSection.2a48034c4c',
|
||||
|
||||
@@ -23932,5 +23932,25 @@
|
||||
},
|
||||
"checks": {
|
||||
"rerunUnsuccessful": "Rerun unsuccessful"
|
||||
},
|
||||
"macosUpdateRequirement": {
|
||||
"title": "Orca updates now need macOS {{macos}}",
|
||||
"description": "Orca {{version}} requires macOS {{macos}} or later. This version keeps working, but it won’t get new updates on this Mac.",
|
||||
"checking": "Checking whether this Mac can upgrade…",
|
||||
"upgradeDescription": "Your Mac can upgrade. Update macOS to get the latest Orca.",
|
||||
"unsupportedDescription": "Apple doesn’t support macOS {{macos}} on this Mac.",
|
||||
"unknownDescription": "We couldn’t confirm an upgrade for this Mac. Apple’s compatibility guide can help.",
|
||||
"whyTitle": "Why is this changing?",
|
||||
"whyIntro": "We updated Electron, the foundation Orca runs on, to bring:",
|
||||
"whySecurity": "Ongoing security fixes from Electron and Chromium.",
|
||||
"whyPerformance": "Faster startup and more efficient native API calls.",
|
||||
"whySystemApis": "Support for current macOS notification APIs and secure Keychain storage.",
|
||||
"whyRequirement": "That newer foundation requires macOS {{macos}} or later.",
|
||||
"dismiss": "Got it",
|
||||
"checkingAction": "Checking your Mac…",
|
||||
"updateAction": "How to update macOS",
|
||||
"discussionAction": "Join the discussion",
|
||||
"checkAction": "Check compatibility",
|
||||
"settingsStatus": "Orca {{version}} requires macOS {{macos}} or later. This is the latest version for this Mac."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,8 @@ export type UISlicePersistence = {
|
||||
setUpdateCardCollapsed: (collapsed: boolean) => void
|
||||
updateReassuranceSeen: boolean
|
||||
markUpdateReassuranceSeen: () => void
|
||||
seenMacosUpdateRequirement: string | null
|
||||
markMacosUpdateRequirementSeen: (minimumMacosVersion: string) => void
|
||||
/** True on the launch where the OSC 52 default-on migration overrode a persisted `false`. */
|
||||
osc52ClipboardDefaultOnNoticePending: boolean
|
||||
clearOsc52ClipboardDefaultOnNotice: () => void
|
||||
|
||||
@@ -217,6 +217,7 @@ export function createUiHydrationActions(set: UISliceSet, _get: UISliceGet): Par
|
||||
? ui.releaseChannelOverride
|
||||
: null,
|
||||
updateReassuranceSeen: ui.updateReassuranceSeen ?? false,
|
||||
seenMacosUpdateRequirement: ui.seenMacosUpdateRequirement ?? null,
|
||||
osc52ClipboardDefaultOnNoticePending: ui.osc52ClipboardDefaultOnNoticePending === true,
|
||||
browserDefaultUrl: ui.browserDefaultUrl ?? null,
|
||||
browserDefaultSearchEngine: ui.browserDefaultSearchEngine ?? null,
|
||||
|
||||
@@ -101,6 +101,13 @@ export function createUiUpdateActions(set: UISliceSet, get: UISliceGet): Partial
|
||||
void window.api.ui.set({ updateReassuranceSeen: true }).catch(console.error)
|
||||
set({ updateReassuranceSeen: true })
|
||||
},
|
||||
seenMacosUpdateRequirement: null,
|
||||
markMacosUpdateRequirementSeen: (minimumMacosVersion) => {
|
||||
void window.api.ui
|
||||
.set({ seenMacosUpdateRequirement: minimumMacosVersion })
|
||||
.catch(console.error)
|
||||
set({ seenMacosUpdateRequirement: minimumMacosVersion })
|
||||
},
|
||||
osc52ClipboardDefaultOnNoticePending: false,
|
||||
clearOsc52ClipboardDefaultOnNotice: () => {
|
||||
// Clear locally before persisting to prevent repeat toasts on failure; next launch may re-arm the notice.
|
||||
|
||||
@@ -12,6 +12,7 @@ export function createWebAppApi(): Partial<PreloadApi> {
|
||||
isProfileStateSaveDelayed: () => Promise.resolve(false),
|
||||
onProfileStateSaveDelayChanged: () => () => undefined,
|
||||
getWindowsAdministratorStatus: () => Promise.resolve(null),
|
||||
getMacosUpgradeSupport: () => Promise.resolve('unknown'),
|
||||
restartAsAdministrator: () =>
|
||||
Promise.reject(new Error('Administrator restart requires the Windows desktop app.')),
|
||||
getIdentity: () =>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** Whether Apple supports the macOS an update needs on this Mac. */
|
||||
export type MacosUpgradeSupport = 'supported' | 'unsupported' | 'unknown'
|
||||
@@ -156,6 +156,8 @@ export type PersistedUIState = {
|
||||
notificationPermissionRequested?: boolean
|
||||
/** Once the "your sessions won't be interrupted" reassurance card is seen, never show it again. */
|
||||
updateReassuranceSeen?: boolean
|
||||
/** macOS major whose update-requirement notice was seen; a later requirement shows it again. */
|
||||
seenMacosUpdateRequirement?: string | null
|
||||
/** Renderer ui:set acknowledgments by pane/turn timestamp; persist so seen rows stay read after restart. */
|
||||
acknowledgedAgentsByPaneKey?: Record<string, number>
|
||||
/** Renderer ui:set cutoffs hide events at/before the timestamp; persist so cleared rows stay hidden after restart. */
|
||||
|
||||
@@ -202,6 +202,7 @@ export const UiUpdateFields = z
|
||||
releaseChannelOverride: z.custom<ReleaseChannel>(isReleaseChannel).nullable().optional(),
|
||||
notificationPermissionRequested: z.boolean().optional(),
|
||||
updateReassuranceSeen: z.boolean().optional(),
|
||||
seenMacosUpdateRequirement: NullableString.optional(),
|
||||
osc52ClipboardDefaultOnNoticePending: z.boolean().optional(),
|
||||
acknowledgedAgentsByPaneKey: z.record(z.string(), z.number().finite()).optional(),
|
||||
activityClearedAtByPaneKey: z.record(z.string(), z.number().finite()).optional(),
|
||||
|
||||
@@ -62,6 +62,13 @@ export type LinuxPackageInstallInstructions =
|
||||
| { ok: true; command: string; packageFileName: string }
|
||||
| { ok: false; reason: LinuxPackageCommandUnavailableReason; message: string }
|
||||
|
||||
/** A newer release this Mac's macOS is too old to run; the updater keeps the current build. */
|
||||
export type UnsupportedSystemUpdate = {
|
||||
version: string
|
||||
/** Major macOS release the update needs, e.g. "13". */
|
||||
minimumMacosVersion: string
|
||||
}
|
||||
|
||||
export type UpdateInstallBlocker = {
|
||||
pid: number
|
||||
kind: 'window' | 'server' | 'browser' | 'unknown'
|
||||
@@ -92,6 +99,8 @@ export type UpdateStatus = (
|
||||
userInitiated?: boolean
|
||||
/** Optional so older paired clients keep rendering "up to date". */
|
||||
delayedUpdate?: DelayedUpdate
|
||||
/** Optional so older paired clients keep rendering "up to date". */
|
||||
unsupportedSystemUpdate?: UnsupportedSystemUpdate
|
||||
}
|
||||
| { state: 'downloading'; percent: number; version: string; activeNudgeId?: string }
|
||||
| {
|
||||
|
||||
@@ -128,6 +128,7 @@ export const UI_STATE_GLOBAL_FIELDS = [
|
||||
'dismissedUpdateNudgeId',
|
||||
'notificationPermissionRequested',
|
||||
'updateReassuranceSeen',
|
||||
'seenMacosUpdateRequirement',
|
||||
'setupGuideSidebarDismissed',
|
||||
'setupGuideBrowserMilestoneMigrated',
|
||||
'setupGuideBrowserMilestoneLegacyComplete',
|
||||
|
||||
Reference in New Issue
Block a user