Files
orca/src/main/index.ts
T
OrcaWinandm4air 47ddfbdc0d feat: let users choose JSON or SQLite when profile copies diverge (#23278)
Replace the copy-a-command startup dialog for diverged JSON/SQLite profile
state with Use SQLite / Use JSON buttons. The choice relaunches Orca into the
locked recovery preflight, applies it, then starts normally. Adds a
current-sqlite recovery selector (and --current-sqlite CLI flag) that archives
the diverged JSON and republishes it from SQLite.

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 17:03:05 -07:00

175 lines
7.0 KiB
TypeScript

import { app, clipboard, dialog, type BrowserWindow } from 'electron'
import { parseSkillShareId } from '../shared/skill-share-link'
import { createMacAppActivationHandler } from './window/macos-app-activation'
import { isBackgroundLaunch } from './window/foreground-activation-policy'
import {
focusExistingWindow as focusExistingWindowAction,
setMainWindowOpener
} from './startup/main-window-actions'
import { openMainWindow as openMainWindowController } from './startup/main-window-controller'
import { mainProcessState as state } from './startup/main-process-state'
import { runMainProcessPreflight } from './startup/main-process-preflight'
import { registerMainProcessIpcHandlers } from './startup/main-process-ipc-bootstrap'
import { initializeMainProcessReady } from './startup/main-process-ready'
import { installMainProcessQuitHandlers } from './startup/main-process-quit'
import { shouldActivateDesktopForSecondInstance } from './startup/single-instance-lock'
import { resolveOpenedMarkdownDocuments } from './startup/os-opened-markdown-files'
import {
formatProfileStateStartupFailure,
isDivergedProfileStateFailure,
profileStateStartupFailureClass
} from './persistence/profile-state/profile-state-startup-failure'
import { recordDurableCrashBreadcrumb } from './crash-reporting/durable-crash-breadcrumb'
import {
chooseProfileStateCopy,
presentProfileStateStartupRecoveryDialog,
readProfileStateCopySavedTimes
} from './persistence/profile-state/profile-state-startup-recovery-dialog'
import { profileStateDesktopRecoveryArgs } from './startup/profile-state-recovery-preflight'
function openMainWindow(options: { revealOnDidFinishLoad?: boolean } = {}): BrowserWindow {
return openMainWindowController(options)
}
setMainWindowOpener(openMainWindow)
function focusExistingWindow(): void {
focusExistingWindowAction()
}
function requestDesktopActivation(argv: readonly string[] = []): void {
state.skillShareDeepLinks.capture(argv, (shareId) => {
state.mainWindow?.webContents.send('ui:openSkillShare', shareId)
})
state.osOpenedMarkdownFiles.capture(argv, publishOsOpenedMarkdownFiles)
// Why: a duplicate `orca serve` must not drag a headless server into opening a desktop window (#11935).
if (!shouldActivateDesktopForSecondInstance(argv)) {
return
}
state.desktopActivationGate?.requestActivation()
}
/**
* Hands buffered OS-opened markdown paths to a renderer that has proven it is listening.
*
* Until that proof arrives the paths stay buffered, because `webContents.send` to a renderer
* with no listener attached is dropped silently and the queue would be gone.
*/
function publishOsOpenedMarkdownFiles(): void {
const targetWindow = state.mainWindow
if (!state.markdownFileOpenListenerReady || !targetWindow || targetWindow.isDestroyed()) {
return
}
// Why consumed before the await: a renderer pull racing this resolve must not take the same
// batch again. The restore() calls hand it back if delivery turns out to be impossible.
const filePaths = state.osOpenedMarkdownFiles.consume()
if (filePaths.length === 0) {
return
}
void resolveOpenedMarkdownDocuments(filePaths)
.then((documents) => {
if (targetWindow.isDestroyed() || targetWindow.webContents.isDestroyed()) {
state.osOpenedMarkdownFiles.restore(filePaths)
return
}
if (documents.length > 0) {
targetWindow.webContents.send('ui:openMarkdownFiles', documents)
}
})
.catch((error) => {
state.osOpenedMarkdownFiles.restore(filePaths)
console.warn('[os-open] Failed to resolve OS-opened markdown files:', error)
})
}
const handleMacAppActivation = createMacAppActivationHandler({
getWindow: () => state.mainWindow,
requestActivation: requestDesktopActivation
})
const preflightReady = runMainProcessPreflight({
focusExistingWindow,
requestDesktopActivation
})
// Why: when another process holds the lock we've already exited; skip file-writing side effects so this transient process never touches userData.
if (preflightReady) {
app.on('open-url', (event, url) => {
if (!parseSkillShareId(url)) {
return
}
event.preventDefault()
requestDesktopActivation([url])
})
// Why: macOS delivers "Open With" as open-file, often before `ready`, and only to a handler
// that claims the event. Non-markdown paths stay unclaimed so the OS default handler wins.
app.on('open-file', (event, filePath) => {
if (!state.osOpenedMarkdownFiles.captureFilePaths([filePath], publishOsOpenedMarkdownFiles)) {
return
}
event.preventDefault()
// Why gated on isReady: pre-ready the cold-start window is already on its way, and
// activating the gate here would try to open one before Electron can.
if (app.isReady()) {
requestDesktopActivation()
}
})
state.skillShareDeepLinks.capture(process.argv)
// Why no publish: nothing is listening this early, so the first renderer pulls these on mount.
state.osOpenedMarkdownFiles.capture(process.argv)
registerMainProcessIpcHandlers()
installMainProcessQuitHandlers()
void app.whenReady().then(async () => {
try {
await initializeMainProcessReady({
openMainWindow,
handleMacAppActivation
})
} catch (error) {
const message =
formatProfileStateStartupFailure(error) ??
`Orca could not finish starting: ${error instanceof Error ? error.message : String(error)}`
const failureClass = profileStateStartupFailureClass(error)
if (failureClass !== undefined) {
recordDurableCrashBreadcrumb('profile_state_startup_failed', {
failure_class: failureClass
})
}
console.error(`[profile-state] ${message}`)
if (!state.isServeMode && !isBackgroundLaunch()) {
try {
if (isDivergedProfileStateFailure(error)) {
const userDataPath = app.getPath('userData')
const choice = await chooseProfileStateCopy({
...readProfileStateCopySavedTimes(userDataPath),
showMessageBox: (options) => dialog.showMessageBox(options)
})
if (choice !== undefined) {
// Recovery needs both profile locks, which only a fresh process can own safely.
app.relaunch({
args: profileStateDesktopRecoveryArgs(process.argv, {
userDataPath,
selector: { kind: choice }
})
})
}
app.exit(1)
return
}
await presentProfileStateStartupRecoveryDialog({
message,
...(failureClass === 'recovery-required' || failureClass === 'ambiguous-authority'
? { recoveryCommand: 'orca profile state exports' }
: {}),
showMessageBox: (options) => dialog.showMessageBox(options),
copyToClipboard: (text) => clipboard.writeText(text)
})
} catch (dialogError) {
console.warn('[profile-state] Recovery dialog failed; exiting safely:', dialogError)
}
}
app.exit(1)
}
})
}