feat(app): redesign discovery and align skeletons with rendered UI

This commit is contained in:
Hampus authored and GitHub committed 2026-08-16 19:17:28 +02:00
1 parent e054aa96be
commit 98c40c6979
380 files changed
+50869 -32207

No files matched your search

+4 -21
View File
@@ -14,7 +14,6 @@ const MINIMIZE_TO_TRAY_STORAGE_KEY_V2 = 'minimizeToTrayV2';
const CLOSE_TO_TRAY_STORAGE_KEY_V2 = 'closeToTrayV2';
interface DesktopConfig extends Record<string, unknown> {
app_url?: string;
chromiumSwitches?: ChromiumSwitchesSetting;
window_behavior?: PersistedDesktopWindowBehaviorSettings;
troubleshooting?: PersistedDesktopTroubleshootingSettings;
@@ -144,11 +143,7 @@ function sanitizeDesktopConfig(value: unknown): DesktopConfig {
return {};
}
const nextConfig: DesktopConfig = {...value};
if (typeof value.app_url === 'string') {
nextConfig.app_url = value.app_url;
} else {
delete nextConfig.app_url;
}
delete nextConfig.app_url;
const chromiumSwitches = sanitizeChromiumSwitchesSetting(value.chromiumSwitches);
if (chromiumSwitches) {
nextConfig.chromiumSwitches = chromiumSwitches;
@@ -300,7 +295,7 @@ function saveDesktopConfig(): void {
try {
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), 'utf-8');
fs.renameSync(tempPath, configPath);
log.debug('Saved desktop config to', configPath, {app_url: config.app_url ?? '(default)'});
log.debug('Saved desktop config to', configPath);
} catch (error) {
log.error('Failed to save desktop config:', error);
try {
@@ -315,7 +310,7 @@ export function loadDesktopConfig(userDataPath: string): void {
if (fs.existsSync(configPath)) {
const data = fs.readFileSync(configPath, 'utf-8');
config = sanitizeDesktopConfig(JSON.parse(data));
log.info('Loaded desktop config from', configPath, {app_url: config.app_url ?? '(default)'});
log.info('Loaded desktop config from', configPath);
}
} catch (error) {
log.error('Failed to load desktop config:', error);
@@ -326,29 +321,17 @@ export function getAppUrl(): string {
if (runtimeAppUrlOverride) {
return runtimeAppUrlOverride;
}
if (config.app_url) {
return config.app_url;
}
return BUILD_CHANNEL === 'canary' ? CANARY_APP_URL : STABLE_APP_URL;
}
export function getCustomAppUrl(): string | null {
return runtimeAppUrlOverride ?? config.app_url ?? null;
return runtimeAppUrlOverride;
}
export function setRuntimeAppUrlOverride(appUrl: string | null): void {
runtimeAppUrlOverride = appUrl;
}
export function setCustomAppUrl(appUrl: string | null): void {
if (appUrl) {
config.app_url = appUrl;
} else {
delete config.app_url;
}
saveDesktopConfig();
}
export function getConfiguredChromiumSwitches(): ChromiumSwitchesSetting | undefined {
return sanitizeChromiumSwitchesSetting(config.chromiumSwitches);
}
-7
View File
@@ -251,11 +251,6 @@ export interface DownloadFileResult {
error?: string;
}
export interface SwitchInstanceUrlOptions {
instanceUrl: string;
desktopHandoffCode?: string | null;
}
export type MediaAccessType = 'microphone' | 'camera' | 'screen' | 'audio-capture';
export type MediaAccessStatus = 'granted' | 'denied' | 'not-determined' | 'restricted' | 'unknown';
export type InputMonitoringPermissionStatus = 'granted' | 'denied' | 'not-determined' | 'unsupported';
@@ -780,8 +775,6 @@ export interface ElectronAPI {
passkeyIsSupported: () => Promise<boolean>;
passkeyAuthenticate: (options: PublicKeyCredentialRequestOptionsJSON) => Promise<AuthenticationResponseJSON>;
passkeyRegister: (options: PublicKeyCredentialCreationOptionsJSON) => Promise<RegistrationResponseJSON>;
switchInstanceUrl: (options: SwitchInstanceUrlOptions) => Promise<void>;
consumeDesktopHandoffCode: () => Promise<string | null>;
virtmic: VirtmicApi;
nativeAudio: NativeAudioApi;
nativeScreenCapture: NativeScreenCaptureApi;
-103
View File
@@ -5,14 +5,12 @@ import {
type DesktopWindowBehaviorSettings,
getDesktopTroubleshootingSettings,
getDesktopWindowBehaviorSettings,
setCustomAppUrl,
setDesktopWindowBehaviorSettings,
} from '@electron/common/DesktopConfig';
import type {
ClipboardWriteFileResult,
DownloadFileResult,
MediaAccessType,
SwitchInstanceUrlOptions,
TrayPresenceStatus,
} from '@electron/common/Types';
import {hasEnabledBlinkFeature, MIDDLE_CLICK_AUTOSCROLL_BLINK_FEATURE} from '@electron/main/ChromiumRuntime';
@@ -92,61 +90,10 @@ interface TrayRuntimeStateUpdate {
buildInfo?: string | null;
}
let pendingDesktopHandoffCode: string | null = null;
function normalizeInstanceOrigin(rawUrl: string): string {
const trimmed = rawUrl.trim();
if (!trimmed) {
throw new Error('Instance URL is required');
}
let candidate = trimmed;
if (!/^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//.test(candidate)) {
candidate = `https://${candidate}`;
}
let url: URL;
try {
url = new URL(candidate);
} catch {
throw new Error('Invalid instance URL');
}
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
throw new Error('Instance URL must use http or https');
}
return url.origin;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object';
}
function isValidWellKnownPayload(payload: unknown): boolean {
if (!isRecord(payload)) {
return false;
}
if (!('endpoints' in payload)) {
return false;
}
const endpoints = (
payload as {
endpoints?: unknown;
}
).endpoints;
if (!endpoints || typeof endpoints !== 'object') {
return false;
}
const api = (
endpoints as {
api?: unknown;
}
).api;
const gateway = (
endpoints as {
gateway?: unknown;
}
).gateway;
return typeof api === 'string' && typeof gateway === 'string';
}
function normalizeDesktopWindowBehaviorUpdate(value: unknown): Partial<DesktopWindowBehaviorSettings> {
if (!isRecord(value)) {
return {};
@@ -190,59 +137,9 @@ function getActiveMiddleClickAutoscroll(): boolean {
return process.platform === 'linux' && hasEnabledBlinkFeature(MIDDLE_CLICK_AUTOSCROLL_BLINK_FEATURE);
}
async function assertValidFluxerInstance(instanceOrigin: string): Promise<void> {
const url = new URL('/.well-known/fluxer', instanceOrigin).toString();
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
},
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const payload = (await response.json()) as unknown;
if (!isValidWellKnownPayload(payload)) {
throw new Error('Malformed discovery document');
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Not a valid Fluxer instance (${message})`);
} finally {
clearTimeout(timeout);
}
}
export function registerIpcHandlers(): void {
registerVoiceDebugEventSinkPopoutIpcHandlers();
registerVoiceBackgroundMediaCacheHandlers();
ipcMain.handle('switch-instance-url', async (_event, options: SwitchInstanceUrlOptions): Promise<void> => {
const instanceOrigin = normalizeInstanceOrigin(options.instanceUrl);
await assertValidFluxerInstance(instanceOrigin);
const mainWindow = getMainWindow();
if (!mainWindow || mainWindow.isDestroyed()) {
throw new Error('Main window not available');
}
pendingDesktopHandoffCode = options.desktopHandoffCode ?? null;
setCustomAppUrl(instanceOrigin);
try {
await mainWindow.loadURL(instanceOrigin);
} catch (error) {
setCustomAppUrl(null);
pendingDesktopHandoffCode = null;
const detail = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to load instance: ${detail}`);
}
});
ipcMain.handle('consume-desktop-handoff-code', (): string | null => {
const code = pendingDesktopHandoffCode;
pendingDesktopHandoffCode = null;
return code;
});
ipcMain.handle('get-desktop-info', () => getDesktopInfo());
ipcMain.handle('get-gpu-info', () => getGpuInfo());
ipcMain.handle('get-app-metrics', () => getAppMetricsSnapshot());
-4
View File
@@ -49,7 +49,6 @@ import type {
SpellcheckState,
StreamerModeCaptureAppStatus,
StreamingPriorityDiagnostics,
SwitchInstanceUrlOptions,
TextareaContextMenuParams,
TrayActionPayload,
TrayRuntimeStatePayload,
@@ -484,9 +483,6 @@ const api: ElectronAPI = {
options: PublicKeyCredentialCreationOptionsJSON,
requestContext?: {pin?: string},
): Promise<RegistrationResponseJSON> => ipcRenderer.invoke('passkey-register', options, requestContext),
switchInstanceUrl: (options: SwitchInstanceUrlOptions): Promise<void> =>
ipcRenderer.invoke('switch-instance-url', options),
consumeDesktopHandoffCode: (): Promise<string | null> => ipcRenderer.invoke('consume-desktop-handoff-code'),
toggleDevTools: (): void => {
ipcRenderer.send('toggle-devtools');
},