Complete Phase E theming, onboarding, and Windows share integration

Adds theme presets (Toffee/Slate/Evergreen/Lavender) with a "follow system"
mode and high-contrast awareness, a first-run welcome screen, and the
Windows analog of Android's share sheet for a Win32 app: an aerofetch://
protocol handler plus an Explorer "Send to AeroFetch" entry, both routed
through a single-instance lock so a second launch hands its link to the
already-running window instead of opening a duplicate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 08:45:09 -04:00
parent a822a2bb52
commit fa78b13cac
17 changed files with 883 additions and 77 deletions
+23 -1
View File
@@ -16,7 +16,8 @@ import {
type CommandPreviewResult,
type ErrorLogEntry,
type BackupExportResult,
type BackupImportResult
type BackupImportResult,
type SystemThemeInfo
} from '@shared/ipc'
// The surface exposed to the renderer. Keep this thin: it only forwards to IPC.
@@ -99,6 +100,27 @@ const api = {
const listener = (_e: IpcRendererEvent, ev: DownloadEvent): void => cb(ev)
ipcRenderer.on(IpcChannels.downloadEvent, listener)
return () => ipcRenderer.removeListener(IpcChannels.downloadEvent, listener)
},
getSystemTheme: (): Promise<SystemThemeInfo> => ipcRenderer.invoke(IpcChannels.systemThemeGet),
/** Subscribe to OS theme/contrast changes. Returns an unsubscribe function. */
onSystemThemeUpdate: (cb: (info: SystemThemeInfo) => void): (() => void) => {
const listener = (_e: IpcRendererEvent, info: SystemThemeInfo): void => cb(info)
ipcRenderer.on(IpcChannels.systemThemeUpdate, listener)
return () => ipcRenderer.removeListener(IpcChannels.systemThemeUpdate, listener)
},
/** Opens Windows' "Contrast themes" accessibility settings page. */
openHighContrastSettings: (): Promise<void> =>
ipcRenderer.invoke(IpcChannels.openHighContrastSettings),
/** Subscribe to links handed to AeroFetch from outside (aerofetch:// or a
* "Send to" .url file). Returns an unsubscribe function. */
onExternalUrl: (cb: (url: string) => void): (() => void) => {
const listener = (_e: IpcRendererEvent, url: string): void => cb(url)
ipcRenderer.on(IpcChannels.externalUrl, listener)
return () => ipcRenderer.removeListener(IpcChannels.externalUrl, listener)
}
}