@wrongstack/desktop 0.282.0 → 0.282.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main/main.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DesktopAgentBridge
3
- } from "./chunk-F3LGFYUK.js";
3
+ } from "./chunk-AMPSW3AD.js";
4
4
 
5
5
  // src/main/main.ts
6
6
  import * as path3 from "path";
@@ -11,7 +11,6 @@ import {
11
11
  BaseWindow,
12
12
  dialog,
13
13
  ipcMain,
14
- Menu,
15
14
  screen,
16
15
  shell,
17
16
  WebContentsView
@@ -779,7 +778,7 @@ var DesktopRuntimeManager = class extends EventEmitter {
779
778
  const fallbackSession = lastActiveProjectRoot ? openProjectSessions.find((session) => samePath(session.root, lastActiveProjectRoot)) : void 0;
780
779
  const activeSession = activeRuntime?.kind === "project" ? runtimeToSessionState(activeRuntime) : fallbackSession ?? openProjectSessions[0];
781
780
  const activeRoot = activeSession?.root;
782
- const activeRuntimeId2 = activeSession?.runtimeId ?? null;
781
+ const activeRuntimeId = activeSession?.runtimeId ?? null;
783
782
  await atomicWrite2(
784
783
  this.stateFile,
785
784
  `${JSON.stringify(
@@ -787,7 +786,7 @@ var DesktopRuntimeManager = class extends EventEmitter {
787
786
  recentProjects: this.recentProjects,
788
787
  openProjects,
789
788
  openProjectSessions,
790
- activeRuntimeId: activeRuntimeId2,
789
+ activeRuntimeId,
791
790
  activeProjectRoot: activeRoot ?? null,
792
791
  window: this.windowState
793
792
  },
@@ -817,12 +816,12 @@ async function terminateProcessTree(child) {
817
816
  child.kill("SIGTERM");
818
817
  return;
819
818
  }
820
- await new Promise((resolve3) => {
819
+ await new Promise((resolve2) => {
821
820
  let settled = false;
822
821
  const finish = () => {
823
822
  if (settled) return;
824
823
  settled = true;
825
- resolve3();
824
+ resolve2();
826
825
  };
827
826
  const timer = setTimeout(finish, 3e3);
828
827
  timer.unref?.();
@@ -967,11 +966,11 @@ async function findFreePort(startPort, exclude) {
967
966
  throw new Error(`No free local port found near ${startPort}`);
968
967
  }
969
968
  function isPortFree(port) {
970
- return new Promise((resolve3) => {
969
+ return new Promise((resolve2) => {
971
970
  const server = net.createServer();
972
- server.once("error", () => resolve3(false));
971
+ server.once("error", () => resolve2(false));
973
972
  server.once("listening", () => {
974
- server.close(() => resolve3(true));
973
+ server.close(() => resolve2(true));
975
974
  });
976
975
  server.listen(port, "127.0.0.1");
977
976
  });
@@ -981,12 +980,12 @@ function waitForHttpReady(baseUrl, token, timeoutMs) {
981
980
  const url = new URL(baseUrl);
982
981
  url.searchParams.set("token", token);
983
982
  url.searchParams.set("shell", "desktop");
984
- return new Promise((resolve3, reject) => {
983
+ return new Promise((resolve2, reject) => {
985
984
  const probe = () => {
986
985
  const req = http.get(url, (res) => {
987
986
  res.resume();
988
987
  if (res.statusCode && res.statusCode >= 200 && res.statusCode < 500) {
989
- resolve3();
988
+ resolve2();
990
989
  return;
991
990
  }
992
991
  retry();
@@ -1074,6 +1073,9 @@ function preloadPath() {
1074
1073
  function webuiPreloadPath() {
1075
1074
  return fileURLToPath(new URL("../preload/webui-preload.cjs", import.meta.url));
1076
1075
  }
1076
+ function desktopSettingsWorkspaceRoot() {
1077
+ return path2.join(wstackGlobalRoot2(), "settings");
1078
+ }
1077
1079
 
1078
1080
  // src/main/main.ts
1079
1081
  import { watchProviderConfig } from "@wrongstack/core/storage";
@@ -1232,14 +1234,356 @@ function isRecord(value) {
1232
1234
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1233
1235
  }
1234
1236
 
1235
- // src/main/main.ts
1236
- var manager = new DesktopRuntimeManager();
1237
- var bridge = new DesktopAgentBridge();
1238
- var OPEN_EXTERNAL_ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
1237
+ // src/main/state/constants.ts
1239
1238
  var SIDEBAR_WIDTH_WIDE = 292;
1240
1239
  var SIDEBAR_WIDTH_MEDIUM = 276;
1241
1240
  var SIDEBAR_WIDTH_NARROW = 252;
1242
1241
  var SIDEBAR_WIDTH_COLLAPSED = 56;
1242
+
1243
+ // src/main/layout/sidebar.ts
1244
+ function getSidebarWidth(windowWidth, collapsed) {
1245
+ if (collapsed) return SIDEBAR_WIDTH_COLLAPSED;
1246
+ if (windowWidth < 900) return SIDEBAR_WIDTH_NARROW;
1247
+ if (windowWidth < 1180) return SIDEBAR_WIDTH_MEDIUM;
1248
+ return SIDEBAR_WIDTH_WIDE;
1249
+ }
1250
+
1251
+ // src/main/menu/index.ts
1252
+ import { Menu } from "electron";
1253
+
1254
+ // src/main/menu/projects-menu.ts
1255
+ var posix = (await import("path")).posix;
1256
+ function buildProjectsMenu(runtimes, actions, t) {
1257
+ const projectGroups = groupProjectRuntimesForMenu(runtimes);
1258
+ const menu = [
1259
+ {
1260
+ label: t("openProjectEllipsis"),
1261
+ accelerator: "CmdOrCtrl+O",
1262
+ click: () => actions.newSession("")
1263
+ // Will open dialog
1264
+ },
1265
+ { label: t("registerProjectEllipsis"), click: () => actions.newSession("") },
1266
+ { type: "separator" }
1267
+ ];
1268
+ if (projectGroups.length === 0) {
1269
+ menu.push({ label: t("noOpenProjectSessions"), enabled: false });
1270
+ return menu;
1271
+ }
1272
+ for (const group of projectGroups) {
1273
+ menu.push({
1274
+ label: group.name,
1275
+ submenu: [
1276
+ {
1277
+ label: t("newSession"),
1278
+ click: () => actions.newSession(group.sessions[0]?.id ?? ""),
1279
+ enabled: Boolean(group.sessions[0])
1280
+ },
1281
+ {
1282
+ label: t("revealProjectFolder"),
1283
+ click: () => actions.reveal(group.sessions[0]?.id ?? ""),
1284
+ enabled: Boolean(group.sessions[0])
1285
+ },
1286
+ { type: "separator" },
1287
+ ...group.sessions.map(
1288
+ (runtime, index) => buildSessionMenu(runtime, index + 1, actions, t)
1289
+ )
1290
+ ]
1291
+ });
1292
+ }
1293
+ return menu;
1294
+ }
1295
+ function buildSessionMenu(runtime, index, actions, t) {
1296
+ const running = runtime.status === "running";
1297
+ const label = `${t("session")} ${index} \xB7 ${runtime.status}`;
1298
+ return {
1299
+ label,
1300
+ submenu: [
1301
+ {
1302
+ label: t("quickView"),
1303
+ click: () => actions.activate(runtime.id)
1304
+ },
1305
+ {
1306
+ label: "WebUI",
1307
+ enabled: running,
1308
+ submenu: [
1309
+ {
1310
+ label: t("chat"),
1311
+ click: () => actions.activateAndNavigate(runtime.id, { activity: "chat", view: "chat" })
1312
+ },
1313
+ {
1314
+ label: t("focusPrompt"),
1315
+ click: () => actions.activateAndNavigate(runtime.id, { action: "focus-chat" })
1316
+ },
1317
+ {
1318
+ label: t("terminal"),
1319
+ click: () => actions.activateAndNavigate(runtime.id, { terminal: "toggle" })
1320
+ },
1321
+ {
1322
+ label: t("newTerminal"),
1323
+ click: () => actions.activateAndNavigate(runtime.id, { terminal: "new" })
1324
+ },
1325
+ { type: "separator" },
1326
+ {
1327
+ label: t("files"),
1328
+ click: () => actions.activateAndNavigate(runtime.id, { activity: "files", view: "files" })
1329
+ },
1330
+ {
1331
+ label: t("changes"),
1332
+ click: () => actions.activateAndNavigate(runtime.id, { activity: "changes", view: "changes" })
1333
+ },
1334
+ {
1335
+ label: t("sessions"),
1336
+ click: () => actions.activateAndNavigate(runtime.id, { view: "sessions" })
1337
+ },
1338
+ {
1339
+ label: t("fleetHQ"),
1340
+ click: () => actions.activateAndNavigate(runtime.id, { activity: "officemap", view: "officemap" })
1341
+ },
1342
+ {
1343
+ label: t("settings"),
1344
+ click: () => actions.activateAndNavigate(runtime.id, { view: "settings" })
1345
+ },
1346
+ { type: "separator" },
1347
+ {
1348
+ label: t("commandPalette"),
1349
+ click: () => actions.activateAndNavigate(runtime.id, { action: "open-command-palette" })
1350
+ },
1351
+ {
1352
+ label: t("modelSwitcher"),
1353
+ click: () => actions.activateAndNavigate(runtime.id, { action: "open-model-switcher" })
1354
+ }
1355
+ ]
1356
+ },
1357
+ { type: "separator" },
1358
+ {
1359
+ label: t("openInBrowser"),
1360
+ enabled: running,
1361
+ click: () => actions.openBrowser(runtime.id)
1362
+ },
1363
+ {
1364
+ label: t("reloadWebui"),
1365
+ enabled: running,
1366
+ click: () => actions.reload(runtime.id)
1367
+ },
1368
+ {
1369
+ label: t("closeSession"),
1370
+ click: () => actions.close(runtime.id)
1371
+ }
1372
+ ]
1373
+ };
1374
+ }
1375
+ function groupProjectRuntimesForMenu(runtimes) {
1376
+ const groups = /* @__PURE__ */ new Map();
1377
+ for (const runtime of runtimes) {
1378
+ if (runtime.kind !== "project") continue;
1379
+ const key = normalizeMenuRoot(runtime.root);
1380
+ const existing = groups.get(key);
1381
+ if (existing) {
1382
+ existing.sessions.push(runtime);
1383
+ continue;
1384
+ }
1385
+ groups.set(key, {
1386
+ key,
1387
+ name: posix.basename(runtime.root) || runtime.name,
1388
+ root: runtime.root,
1389
+ sessions: [runtime]
1390
+ });
1391
+ }
1392
+ return [...groups.values()].sort((a, b) => a.name.localeCompare(b.name));
1393
+ }
1394
+ function normalizeMenuRoot(root) {
1395
+ return root.replace(/\\/g, "/").replace(/\/+$/g, "").toLowerCase();
1396
+ }
1397
+
1398
+ // src/main/menu/sections.ts
1399
+ function buildFileMenu(ctx, _actions, hasActiveRuntime, hasActiveProjectWebui, active, navigate, getActiveRuntimeId) {
1400
+ return {
1401
+ label: ctx.t("file"),
1402
+ submenu: [
1403
+ {
1404
+ label: ctx.t("openProjectEllipsis"),
1405
+ accelerator: "CmdOrCtrl+O",
1406
+ click: () => void ctx.openProject()
1407
+ },
1408
+ { label: ctx.t("registerProjectEllipsis"), click: () => void ctx.registerProject() },
1409
+ {
1410
+ label: ctx.t("removeActiveFromRegistry"),
1411
+ enabled: hasActiveProjectWebui,
1412
+ click: () => {
1413
+ if (active?.kind === "project") void ctx.unregisterProject(active.root);
1414
+ }
1415
+ },
1416
+ { type: "separator" },
1417
+ {
1418
+ label: ctx.t("newSessionForActive"),
1419
+ accelerator: "CmdOrCtrl+N",
1420
+ enabled: hasActiveProjectWebui,
1421
+ click: () => void ctx.openProjectSession(active?.id)
1422
+ },
1423
+ {
1424
+ label: ctx.t("settings"),
1425
+ accelerator: "CmdOrCtrl+,",
1426
+ click: () => {
1427
+ if (hasActiveRuntime) navigate({ view: "settings" });
1428
+ else void ctx.openSettings();
1429
+ }
1430
+ },
1431
+ { type: "separator" },
1432
+ {
1433
+ label: ctx.t("closeActiveRuntime"),
1434
+ accelerator: "CmdOrCtrl+W",
1435
+ enabled: hasActiveRuntime,
1436
+ click: () => {
1437
+ const id = getActiveRuntimeId();
1438
+ if (id) void ctx.closeRuntime(id);
1439
+ }
1440
+ },
1441
+ { type: "separator" },
1442
+ {
1443
+ role: process.platform === "darwin" ? "close" : "quit"
1444
+ }
1445
+ ]
1446
+ };
1447
+ }
1448
+ function buildWorkspaceMenu(ctx, _actions, hasActiveWebui, prefs, navigate) {
1449
+ const yoloChecked = prefs?.yolo === true;
1450
+ const nextPredictionChecked = prefs?.nextPrediction === true;
1451
+ const contextAutoCompactChecked = prefs?.contextAutoCompact === true;
1452
+ const webuiItem = (item) => ({
1453
+ ...item,
1454
+ enabled: item.enabled ?? hasActiveWebui
1455
+ });
1456
+ return {
1457
+ label: ctx.t("workspace"),
1458
+ submenu: [
1459
+ webuiItem({
1460
+ label: ctx.t("openChat"),
1461
+ accelerator: "CmdOrCtrl+1",
1462
+ click: () => navigate({ activity: "chat", view: "chat" })
1463
+ }),
1464
+ webuiItem({
1465
+ label: ctx.t("focusPrompt"),
1466
+ accelerator: "CmdOrCtrl+/",
1467
+ click: () => navigate({ action: "focus-chat" })
1468
+ }),
1469
+ webuiItem({
1470
+ label: ctx.t("toggleTerminal"),
1471
+ accelerator: "CmdOrCtrl+`",
1472
+ click: () => navigate({ terminal: "toggle" })
1473
+ }),
1474
+ webuiItem({ label: ctx.t("newTerminal"), click: () => navigate({ terminal: "new" }) }),
1475
+ { type: "separator" },
1476
+ webuiItem({
1477
+ label: ctx.t("commandPalette"),
1478
+ accelerator: "CmdOrCtrl+K",
1479
+ click: () => navigate({ action: "open-command-palette" })
1480
+ }),
1481
+ webuiItem({
1482
+ label: ctx.t("quickModelSwitcher"),
1483
+ accelerator: "CmdOrCtrl+M",
1484
+ click: () => navigate({ action: "open-model-switcher" })
1485
+ }),
1486
+ webuiItem({
1487
+ type: "checkbox",
1488
+ label: ctx.t("yoloMode"),
1489
+ checked: yoloChecked,
1490
+ accelerator: "CmdOrCtrl+Shift+Y",
1491
+ click: () => navigate({ pref: { key: "yolo", toggle: true } })
1492
+ }),
1493
+ webuiItem({
1494
+ type: "checkbox",
1495
+ label: ctx.t("nextPrediction"),
1496
+ checked: nextPredictionChecked,
1497
+ click: () => navigate({ pref: { key: "nextPrediction", toggle: true } })
1498
+ }),
1499
+ webuiItem({
1500
+ type: "checkbox",
1501
+ label: ctx.t("contextAutoCompact"),
1502
+ checked: contextAutoCompactChecked,
1503
+ click: () => navigate({ pref: { key: "contextAutoCompact", toggle: true } })
1504
+ }),
1505
+ { type: "separator" },
1506
+ webuiItem({
1507
+ label: ctx.t("reloadActiveWebui"),
1508
+ accelerator: "CmdOrCtrl+Shift+R",
1509
+ click: () => void ctx.reloadActiveWebuiView()
1510
+ })
1511
+ ]
1512
+ };
1513
+ }
1514
+ function buildViewMenu(ctx) {
1515
+ return {
1516
+ label: ctx.t("view"),
1517
+ submenu: [
1518
+ {
1519
+ type: "checkbox",
1520
+ label: ctx.t("compactDesktopSidebar"),
1521
+ accelerator: "CmdOrCtrl+B",
1522
+ checked: ctx.getShellSidebarCollapsed(),
1523
+ click: () => ctx.setShellSidebarCollapsed(!ctx.getShellSidebarCollapsed())
1524
+ },
1525
+ { type: "separator" },
1526
+ { role: "reload" },
1527
+ { role: "toggleDevTools" },
1528
+ { type: "separator" },
1529
+ { role: "resetZoom" },
1530
+ { role: "zoomIn" },
1531
+ { role: "zoomOut" },
1532
+ { type: "separator" },
1533
+ { role: "togglefullscreen" }
1534
+ ]
1535
+ };
1536
+ }
1537
+
1538
+ // src/main/menu/index.ts
1539
+ function configureApplicationMenu(ctx) {
1540
+ const snapshot = ctx.getSnapshot();
1541
+ const active = ctx.getActiveRuntime();
1542
+ const hasActiveRuntime = Boolean(active);
1543
+ const hasActiveWebui = active?.status === "running";
1544
+ const hasActiveProjectWebui = hasActiveWebui && active?.kind === "project";
1545
+ const activeWebuiPrefs = ctx.getActiveWebuiPrefs();
1546
+ const navigate = (command) => {
1547
+ void ctx.dispatchWebuiCommand(command);
1548
+ };
1549
+ const activateAndNavigate = (runtimeId, command) => {
1550
+ void ctx.activateRuntime(runtimeId).then(() => ctx.dispatchWebuiCommand(command));
1551
+ };
1552
+ const reloadRuntimeWebui = (runtimeId) => {
1553
+ void ctx.activateRuntime(runtimeId).then(() => ctx.reloadActiveWebuiView());
1554
+ };
1555
+ const actions = {
1556
+ activate: (runtimeId) => void ctx.activateRuntime(runtimeId),
1557
+ activateAndNavigate,
1558
+ newSession: (runtimeId) => {
1559
+ if (runtimeId) void ctx.openProjectSession(runtimeId);
1560
+ else void ctx.openProject();
1561
+ },
1562
+ openBrowser: (runtimeId) => {
1563
+ const url = ctx.getRuntimeManager().getRuntimeUrlWithToken(runtimeId);
1564
+ if (url) ctx.openExternal(url);
1565
+ },
1566
+ reload: reloadRuntimeWebui,
1567
+ close: (runtimeId) => void ctx.closeRuntime(runtimeId),
1568
+ reveal: (runtimeId) => {
1569
+ const runtime = ctx.getRuntimeManager().getRuntime(runtimeId);
1570
+ if (runtime) ctx.revealInExplorer(runtime.root);
1571
+ }
1572
+ };
1573
+ const template = [
1574
+ buildFileMenu(ctx, actions, hasActiveRuntime, hasActiveProjectWebui, active, navigate, ctx.getActiveRuntimeId),
1575
+ {
1576
+ label: ctx.t("projects"),
1577
+ submenu: buildProjectsMenu(snapshot.runtimes, actions, ctx.t)
1578
+ },
1579
+ buildWorkspaceMenu(ctx, actions, hasActiveWebui, activeWebuiPrefs, navigate),
1580
+ buildViewMenu(ctx)
1581
+ ];
1582
+ Menu.setApplicationMenu(Menu.buildFromTemplate(template));
1583
+ }
1584
+
1585
+ // src/main/main.ts
1586
+ var OPEN_EXTERNAL_ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
1243
1587
  var MIN_WINDOW_WIDTH2 = 760;
1244
1588
  var MIN_WINDOW_HEIGHT2 = 520;
1245
1589
  var MAX_PENDING_WEBUI_COMMANDS = 50;
@@ -1248,17 +1592,8 @@ var WEBUI_COMMAND_FALLBACK_MS = 350;
1248
1592
  var WEBUI_COMMAND_ACK_TIMEOUT_MS = 2e3;
1249
1593
  app.setAppUserModelId("com.wrongstack.desktop");
1250
1594
  app.setPath("userData", path3.join(wstackGlobalRoot3(), "desktop", "electron-profile"));
1251
- function safeOpenExternal(target) {
1252
- let protocol;
1253
- try {
1254
- protocol = new URL(target).protocol;
1255
- } catch {
1256
- return;
1257
- }
1258
- if (OPEN_EXTERNAL_ALLOWED_PROTOCOLS.has(protocol)) {
1259
- void shell.openExternal(target);
1260
- }
1261
- }
1595
+ var manager = new DesktopRuntimeManager();
1596
+ var bridge = new DesktopAgentBridge();
1262
1597
  var mainWindow = null;
1263
1598
  var shellView = null;
1264
1599
  var webuiViews = /* @__PURE__ */ new Map();
@@ -1269,77 +1604,56 @@ var shellSidebarCollapsed = false;
1269
1604
  var pendingWebuiCommandAcks = /* @__PURE__ */ new Map();
1270
1605
  var saveWindowStateTimer = null;
1271
1606
  var quittingAfterCleanup = false;
1272
- async function createWindow() {
1273
- await manager.init();
1274
- const bootLocale = await readUiLocale();
1275
- if (bootLocale) setMainLocale(bootLocale);
1276
- configureApplicationMenu();
1277
- const windowState = validatedWindowState(manager.getWindowState());
1278
- const windowOptions = {
1279
- width: windowState?.width ?? 1320,
1280
- height: windowState?.height ?? 860,
1281
- minWidth: MIN_WINDOW_WIDTH2,
1282
- minHeight: MIN_WINDOW_HEIGHT2,
1283
- title: tMain("windowTitle"),
1284
- backgroundColor: "#111217"
1285
- };
1286
- if (windowState?.x !== void 0) windowOptions.x = windowState.x;
1287
- if (windowState?.y !== void 0) windowOptions.y = windowState.y;
1288
- mainWindow = new BaseWindow(windowOptions);
1289
- if (windowState?.maximized) {
1290
- mainWindow.maximize();
1607
+ function safeOpenExternal(target) {
1608
+ let protocol;
1609
+ try {
1610
+ protocol = new URL(target).protocol;
1611
+ } catch {
1612
+ return;
1613
+ }
1614
+ if (OPEN_EXTERNAL_ALLOWED_PROTOCOLS.has(protocol)) {
1615
+ void shell.openExternal(target);
1291
1616
  }
1292
- shellView = new WebContentsView({
1293
- webPreferences: {
1294
- preload: preloadPath(),
1295
- contextIsolation: true,
1296
- nodeIntegration: false,
1297
- sandbox: false
1298
- }
1299
- });
1300
- mainWindow.contentView.addChildView(shellView);
1301
- shellView.webContents.once("did-finish-load", () => {
1302
- shellView?.webContents.send(IPC.localeChanged, getMainLocale());
1303
- });
1304
- shellView.webContents.setWindowOpenHandler(({ url }) => {
1305
- safeOpenExternal(url);
1306
- return { action: "deny" };
1307
- });
1308
- await shellView.webContents.loadFile(rendererIndexPath());
1309
- mainWindow.on("resize", layoutViews);
1310
- mainWindow.on("resize", scheduleWindowStateSave);
1311
- mainWindow.on("move", scheduleWindowStateSave);
1312
- mainWindow.on("maximize", scheduleWindowStateSave);
1313
- mainWindow.on("unmaximize", scheduleWindowStateSave);
1314
- mainWindow.on("close", () => {
1315
- if (saveWindowStateTimer) {
1316
- clearTimeout(saveWindowStateTimer);
1317
- saveWindowStateTimer = null;
1318
- }
1319
- void saveWindowState();
1320
- });
1321
- mainWindow.on("closed", () => {
1322
- if (saveWindowStateTimer) {
1323
- clearTimeout(saveWindowStateTimer);
1324
- saveWindowStateTimer = null;
1325
- }
1326
- mainWindow = null;
1327
- disposeAllWebuiEntries();
1328
- shellView = null;
1329
- activeWebuiRuntimeId = null;
1330
- webuiStatus = { runtimeId: null, status: "idle" };
1331
- });
1332
- layoutViews();
1333
- syncActiveWebuiView();
1334
- void restoreLastWorkspace();
1335
1617
  }
1336
- function layoutViews() {
1337
- if (!mainWindow || !shellView) return;
1338
- const size = mainWindow.getContentSize();
1339
- const width = size[0] ?? 0;
1340
- const height = size[1] ?? 0;
1341
- shellView.setBounds({ x: 0, y: 0, width, height });
1342
- layoutWebuiViews(width, height);
1618
+ function sameOrigin(candidate, base) {
1619
+ if (!base) return false;
1620
+ try {
1621
+ return new URL(candidate).origin === new URL(base).origin;
1622
+ } catch {
1623
+ return false;
1624
+ }
1625
+ }
1626
+ function isRecord2(value) {
1627
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1628
+ }
1629
+ function runtimeWsUrlOrThrow(runtimeId) {
1630
+ const wsUrl = manager.getRuntimeWsUrlWithToken(runtimeId);
1631
+ if (!wsUrl) throw new Error(`Runtime not found: ${runtimeId}`);
1632
+ return wsUrl;
1633
+ }
1634
+ function setShellSidebarCollapsed(collapsed) {
1635
+ shellSidebarCollapsed = collapsed;
1636
+ layoutWebuiViews();
1637
+ configureApplicationMenu2();
1638
+ if (!shellView || shellView.webContents.isDestroyed()) return;
1639
+ shellView.webContents.send(IPC.shellSidebarCollapsedChanged, shellSidebarCollapsed);
1640
+ }
1641
+ function menuRelevantPrefsChanged(previous, next) {
1642
+ return previous?.yolo !== next?.yolo || previous?.nextPrediction !== next?.nextPrediction || previous?.contextAutoCompact !== next?.contextAutoCompact;
1643
+ }
1644
+ function setEntryWebuiStatus(entry, next) {
1645
+ const previousPrefs = entry.status.prefs;
1646
+ entry.status = {
1647
+ ...next,
1648
+ prefs: next.prefs ?? entry.status.prefs,
1649
+ pendingCommands: entry.pendingCommands.length || void 0
1650
+ };
1651
+ if (activeWebuiRuntimeId === entry.runtimeId) {
1652
+ publishWebuiStatus(entry.status);
1653
+ if (menuRelevantPrefsChanged(previousPrefs, entry.status.prefs)) {
1654
+ configureApplicationMenu2();
1655
+ }
1656
+ }
1343
1657
  }
1344
1658
  function scheduleWindowStateSave() {
1345
1659
  if (saveWindowStateTimer) clearTimeout(saveWindowStateTimer);
@@ -1359,36 +1673,51 @@ async function saveWindowState() {
1359
1673
  maximized: mainWindow.isMaximized()
1360
1674
  });
1361
1675
  }
1362
- function layoutWebuiViews(windowWidth, windowHeight) {
1676
+ function validatedWindowState(state) {
1677
+ if (!state) return null;
1678
+ if (state.width < MIN_WINDOW_WIDTH2 || state.height < MIN_WINDOW_HEIGHT2) return null;
1679
+ if (state.x === void 0 || state.y === void 0) return state;
1680
+ const candidate = {
1681
+ x: state.x,
1682
+ y: state.y,
1683
+ width: state.width,
1684
+ height: state.height
1685
+ };
1686
+ const visibleOnSomeDisplay = screen.getAllDisplays().some((display) => {
1687
+ const area = display.workArea;
1688
+ return rectanglesIntersect(candidate, area);
1689
+ });
1690
+ return visibleOnSomeDisplay ? state : null;
1691
+ }
1692
+ function rectanglesIntersect(left, right) {
1693
+ return left.x < right.x + right.width && left.x + left.width > right.x && left.y < right.y + right.height && left.y + left.height > right.y;
1694
+ }
1695
+ function layoutViews() {
1696
+ if (!mainWindow || !shellView) return;
1697
+ const size = mainWindow.getContentSize();
1698
+ const width = size[0] ?? 0;
1699
+ const height = size[1] ?? 0;
1700
+ shellView.setBounds({ x: 0, y: 0, width, height });
1701
+ layoutWebuiViews();
1702
+ }
1703
+ function layoutWebuiViews() {
1363
1704
  if (!mainWindow) return;
1364
1705
  const size = mainWindow.getContentSize();
1365
- const width = windowWidth ?? size[0] ?? 0;
1366
- const height = windowHeight ?? size[1] ?? 0;
1706
+ const width = size[0] ?? 0;
1707
+ const height = size[1] ?? 0;
1367
1708
  const snapshot = manager.snapshot();
1368
1709
  const active = snapshot.runtimes.find((runtime) => runtime.id === snapshot.activeRuntimeId);
1369
- const sidebarWidth = desktopSidebarWidth(width);
1710
+ const sidebarWidth = getSidebarWidth(width, shellSidebarCollapsed);
1370
1711
  const contentWidth = Math.max(0, width - sidebarWidth);
1371
1712
  for (const entry of webuiViews.values()) {
1372
- if (active?.id === entry.runtimeId && active.status === "running") {
1713
+ const runtime = snapshot.runtimes.find((r) => r.id === entry.runtimeId);
1714
+ if (active?.id === entry.runtimeId && runtime?.status === "running") {
1373
1715
  entry.view.setBounds({ x: sidebarWidth, y: 0, width: contentWidth, height });
1374
1716
  } else {
1375
1717
  entry.view.setBounds({ x: sidebarWidth, y: 0, width: 0, height });
1376
1718
  }
1377
1719
  }
1378
1720
  }
1379
- function desktopSidebarWidth(windowWidth) {
1380
- if (shellSidebarCollapsed) return SIDEBAR_WIDTH_COLLAPSED;
1381
- if (windowWidth < 900) return SIDEBAR_WIDTH_NARROW;
1382
- if (windowWidth < 1180) return SIDEBAR_WIDTH_MEDIUM;
1383
- return SIDEBAR_WIDTH_WIDE;
1384
- }
1385
- function setShellSidebarCollapsed(collapsed) {
1386
- shellSidebarCollapsed = collapsed;
1387
- layoutWebuiViews();
1388
- configureApplicationMenu();
1389
- if (!shellView || shellView.webContents.isDestroyed()) return;
1390
- shellView.webContents.send(IPC.shellSidebarCollapsedChanged, shellSidebarCollapsed);
1391
- }
1392
1721
  function ensureWebuiEntry(runtimeId) {
1393
1722
  if (!mainWindow) return null;
1394
1723
  const existing = webuiViews.get(runtimeId);
@@ -1436,11 +1765,7 @@ function ensureWebuiEntry(runtimeId) {
1436
1765
  });
1437
1766
  view.webContents.on("did-fail-load", (_event, errorCode, errorDescription) => {
1438
1767
  if (webuiViews.get(runtimeId) !== entry || errorCode === -3) return;
1439
- setEntryWebuiStatus(entry, {
1440
- runtimeId,
1441
- status: "error",
1442
- error: errorDescription
1443
- });
1768
+ setEntryWebuiStatus(entry, { runtimeId, status: "error", error: errorDescription });
1444
1769
  });
1445
1770
  view.webContents.on("render-process-gone", (_event, details) => {
1446
1771
  if (webuiViews.get(runtimeId) !== entry) return;
@@ -1453,6 +1778,48 @@ function ensureWebuiEntry(runtimeId) {
1453
1778
  webuiViews.set(runtimeId, entry);
1454
1779
  return entry;
1455
1780
  }
1781
+ function attachWebuiEntry(entry) {
1782
+ if (!mainWindow) return;
1783
+ if (entry.attached) return;
1784
+ mainWindow.contentView.addChildView(entry.view);
1785
+ entry.attached = true;
1786
+ }
1787
+ function disposeWebuiEntry(entry) {
1788
+ webuiViews.delete(entry.runtimeId);
1789
+ entry.pendingCommands.length = 0;
1790
+ settlePendingWebuiCommandAcksForRuntime(entry.runtimeId, false);
1791
+ if (entry.pendingFlushTimer) {
1792
+ clearTimeout(entry.pendingFlushTimer);
1793
+ entry.pendingFlushTimer = null;
1794
+ }
1795
+ if (mainWindow && entry.attached) {
1796
+ mainWindow.contentView.removeChildView(entry.view);
1797
+ }
1798
+ entry.attached = false;
1799
+ if (!entry.view.webContents.isDestroyed()) {
1800
+ entry.view.webContents.close();
1801
+ }
1802
+ if (activeWebuiRuntimeId === entry.runtimeId) activeWebuiRuntimeId = null;
1803
+ }
1804
+ function disposeAllWebuiEntries() {
1805
+ for (const entry of Array.from(webuiViews.values())) {
1806
+ disposeWebuiEntry(entry);
1807
+ }
1808
+ webuiViews.clear();
1809
+ }
1810
+ function pruneWebuiEntries(runtimeIds) {
1811
+ const live = new Set(runtimeIds);
1812
+ for (const [id, entry] of webuiViews) {
1813
+ if (!live.has(id)) {
1814
+ disposeWebuiEntry(entry);
1815
+ }
1816
+ }
1817
+ }
1818
+ function findWebuiEntryBySenderId(senderId) {
1819
+ return Array.from(webuiViews.values()).find(
1820
+ (candidate) => candidate.view.webContents.id === senderId
1821
+ );
1822
+ }
1456
1823
  function syncActiveWebuiView() {
1457
1824
  if (!mainWindow) return;
1458
1825
  const snapshot = manager.snapshot();
@@ -1489,7 +1856,15 @@ function syncActiveWebuiView() {
1489
1856
  status: "error",
1490
1857
  error: err instanceof Error ? err.message : String(err)
1491
1858
  });
1492
- console.error("Failed to load desktop WebUI view:", err);
1859
+ console.error(
1860
+ JSON.stringify({
1861
+ level: "error",
1862
+ event: "desktop.webui_view_load_failed",
1863
+ runtimeId: active.id,
1864
+ message: err instanceof Error ? err.message : String(err),
1865
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1866
+ })
1867
+ );
1493
1868
  });
1494
1869
  }
1495
1870
  }
@@ -1502,28 +1877,6 @@ function publishWebuiStatus(next) {
1502
1877
  if (!shellView || shellView.webContents.isDestroyed()) return;
1503
1878
  shellView.webContents.send(IPC.webuiStatusChanged, webuiStatus);
1504
1879
  }
1505
- function setEntryWebuiStatus(entry, next) {
1506
- const previousPrefs = entry.status.prefs;
1507
- entry.status = {
1508
- ...next,
1509
- prefs: next.prefs ?? entry.status.prefs,
1510
- pendingCommands: entry.pendingCommands.length || void 0
1511
- };
1512
- if (activeWebuiRuntimeId === entry.runtimeId) {
1513
- publishWebuiStatus(entry.status);
1514
- if (menuRelevantPrefsChanged(previousPrefs, entry.status.prefs)) {
1515
- configureApplicationMenu();
1516
- }
1517
- }
1518
- }
1519
- function menuRelevantPrefsChanged(previous, next) {
1520
- return previous?.yolo !== next?.yolo || previous?.nextPrediction !== next?.nextPrediction || previous?.contextAutoCompact !== next?.contextAutoCompact;
1521
- }
1522
- function runtimeWsUrlOrThrow(runtimeId) {
1523
- const wsUrl = manager.getRuntimeWsUrlWithToken(runtimeId);
1524
- if (!wsUrl) throw new Error(`Runtime not found: ${runtimeId}`);
1525
- return wsUrl;
1526
- }
1527
1880
  function broadcastLocaleToEmbeddedWebuis(locale) {
1528
1881
  for (const entry of webuiViews.values()) {
1529
1882
  if (entry.view.webContents.isDestroyed()) continue;
@@ -1533,103 +1886,25 @@ function broadcastLocaleToEmbeddedWebuis(locale) {
1533
1886
  }
1534
1887
  }
1535
1888
  }
1536
- function registerIpc() {
1537
- ipcMain.handle(IPC.getState, () => manager.snapshot());
1538
- ipcMain.handle(IPC.getConversation, (_event, runtimeId) => bridge.snapshot(runtimeId));
1539
- ipcMain.handle(IPC.getWebuiStatus, () => webuiStatus);
1540
- ipcMain.handle(
1541
- IPC.navigateWebui,
1542
- async (_event, command) => dispatchWebuiCommand(command)
1543
- );
1544
- ipcMain.handle(IPC.reloadWebui, async () => reloadActiveWebuiView());
1545
- ipcMain.handle(IPC.setShellSidebarCollapsed, (_event, collapsed) => {
1546
- setShellSidebarCollapsed(collapsed === true);
1547
- return true;
1548
- });
1549
- ipcMain.handle(IPC.openSettings, async () => openSettings());
1550
- ipcMain.handle(IPC.openProjectSession, async (_event, runtimeId) => {
1551
- return openProjectSession(runtimeId);
1552
- });
1553
- ipcMain.handle(IPC.openProject, async (_event, requestedRoot) => {
1554
- return openProject(requestedRoot);
1555
- });
1556
- ipcMain.handle(IPC.registerProject, async (_event, requestedRoot) => {
1557
- return registerProject(requestedRoot);
1558
- });
1559
- ipcMain.handle(IPC.unregisterProject, async (_event, root) => {
1560
- return unregisterProject(root);
1561
- });
1562
- ipcMain.handle(IPC.activateRuntime, async (_event, id) => {
1563
- return activateRuntime(id);
1564
- });
1565
- ipcMain.handle(IPC.closeRuntime, async (_event, id) => {
1566
- return closeRuntime(id);
1567
- });
1568
- ipcMain.handle(
1569
- IPC.sendMessage,
1570
- async (_event, id, content) => bridge.sendMessage(id, runtimeWsUrlOrThrow(id), content)
1571
- );
1572
- ipcMain.handle(
1573
- IPC.abortRuntime,
1574
- async (_event, id) => bridge.abort(id, runtimeWsUrlOrThrow(id))
1575
- );
1576
- ipcMain.handle(IPC.openRuntimeInBrowser, async (_event, id) => {
1577
- const url = manager.getRuntimeUrlWithToken(id);
1578
- if (url) safeOpenExternal(url);
1579
- });
1580
- ipcMain.handle(IPC.revealRuntimeRoot, async (_event, id) => {
1581
- const runtime = manager.getRuntime(id);
1582
- if (runtime) await shell.openPath(runtime.root);
1583
- });
1584
- ipcMain.on(IPC.webuiReadyChanged, (event, ready) => {
1585
- const entry = findWebuiEntryBySenderId(event.sender.id);
1586
- if (!entry) return;
1587
- entry.bridgeReady = ready === true;
1588
- if (entry.bridgeReady) {
1589
- setEntryWebuiStatus(entry, { ...entry.status, status: "ready" });
1590
- schedulePendingWebuiFlush(entry);
1591
- } else if (entry.status.status === "ready") {
1592
- setEntryWebuiStatus(entry, { ...entry.status, status: "loading" });
1593
- }
1594
- });
1595
- ipcMain.on(IPC.webuiPrefsChanged, (event, prefs) => {
1596
- const entry = findWebuiEntryBySenderId(event.sender.id);
1597
- if (!entry) return;
1598
- const sanitized = sanitizeWebuiPrefs(prefs);
1599
- if (Object.keys(sanitized).length === 0) return;
1600
- setEntryWebuiStatus(entry, {
1601
- ...entry.status,
1602
- prefs: { ...entry.status.prefs ?? {}, ...sanitized }
1603
- });
1604
- });
1605
- ipcMain.on(
1606
- IPC.webuiCommandAck,
1607
- (event, requestId, handled, _message) => {
1608
- const entry = findWebuiEntryBySenderId(event.sender.id);
1609
- if (!entry || typeof requestId !== "string") return;
1610
- const pending = pendingWebuiCommandAcks.get(requestId);
1611
- if (!pending || pending.runtimeId !== entry.runtimeId) return;
1612
- settlePendingWebuiCommandAck(requestId, handled === true);
1613
- }
1614
- );
1889
+ function getActiveWebuiEntry() {
1890
+ const activeId = manager.snapshot().activeRuntimeId;
1891
+ return activeId ? webuiViews.get(activeId) : void 0;
1615
1892
  }
1616
- function findWebuiEntryBySenderId(senderId) {
1617
- return Array.from(webuiViews.values()).find(
1618
- (candidate) => candidate.view.webContents.id === senderId
1619
- );
1893
+ async function isWebuiCommandBridgeReady(entry) {
1894
+ if (webuiViews.get(entry.runtimeId) !== entry || !entry.url) return false;
1895
+ return entry.bridgeReady;
1620
1896
  }
1621
- function sanitizeWebuiPrefs(prefs) {
1622
- const next = {};
1623
- if (!isRecord2(prefs)) return next;
1624
- if (typeof prefs["yolo"] === "boolean") next.yolo = prefs["yolo"];
1625
- if (typeof prefs["nextPrediction"] === "boolean") next.nextPrediction = prefs["nextPrediction"];
1626
- if (typeof prefs["contextAutoCompact"] === "boolean") {
1627
- next.contextAutoCompact = prefs["contextAutoCompact"];
1897
+ function queueWebuiCommand(entry, command) {
1898
+ entry.pendingCommands.push(command);
1899
+ if (entry.pendingCommands.length > MAX_PENDING_WEBUI_COMMANDS) {
1900
+ entry.pendingCommands.splice(0, entry.pendingCommands.length - MAX_PENDING_WEBUI_COMMANDS);
1628
1901
  }
1629
- return next;
1902
+ entry.pendingFlushAttempts = 0;
1903
+ setEntryWebuiStatus(entry, entry.status);
1630
1904
  }
1631
- function isRecord2(value) {
1632
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1905
+ function nextWebuiCommandRequestId(runtimeId) {
1906
+ webuiCommandSequence += 1;
1907
+ return `${runtimeId}:${Date.now()}:${webuiCommandSequence}`;
1633
1908
  }
1634
1909
  async function dispatchWebuiCommand(commandInput) {
1635
1910
  const command = normalizeDesktopWebuiCommand(commandInput);
@@ -1665,7 +1940,15 @@ async function reloadActiveWebuiView() {
1665
1940
  status: "error",
1666
1941
  error: err instanceof Error ? err.message : String(err)
1667
1942
  });
1668
- console.error("Failed to reload desktop WebUI view:", err);
1943
+ console.error(
1944
+ JSON.stringify({
1945
+ level: "error",
1946
+ event: "desktop.webui_view_reload_failed",
1947
+ runtimeId: entry.runtimeId,
1948
+ message: err instanceof Error ? err.message : String(err),
1949
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1950
+ })
1951
+ );
1669
1952
  return false;
1670
1953
  });
1671
1954
  }
@@ -1673,7 +1956,7 @@ async function dispatchWebuiCommandNow(entry, command) {
1673
1956
  if (webuiViews.get(entry.runtimeId) !== entry || !entry.url) return false;
1674
1957
  const requestId = nextWebuiCommandRequestId(entry.runtimeId);
1675
1958
  const commandWithRequestId = { ...command, requestId };
1676
- return new Promise((resolve3) => {
1959
+ return new Promise((resolve2) => {
1677
1960
  const fallbackTimer = setTimeout(() => {
1678
1961
  const pending = pendingWebuiCommandAcks.get(requestId);
1679
1962
  if (!pending) return;
@@ -1686,7 +1969,7 @@ async function dispatchWebuiCommandNow(entry, command) {
1686
1969
  runtimeId: entry.runtimeId,
1687
1970
  timer,
1688
1971
  fallbackTimer,
1689
- resolve: resolve3
1972
+ resolve: resolve2
1690
1973
  });
1691
1974
  try {
1692
1975
  entry.view.webContents.send(IPC.webuiCommand, commandWithRequestId);
@@ -1702,10 +1985,6 @@ function sendWebuiCommandDomFallback(entry, command) {
1702
1985
  if (webuiViews.get(entry.runtimeId) !== entry || entry.view.webContents.isDestroyed()) return;
1703
1986
  void entry.view.webContents.executeJavaScript(buildWebuiCommandFallbackScript(command), true).catch(() => void 0);
1704
1987
  }
1705
- function nextWebuiCommandRequestId(runtimeId) {
1706
- webuiCommandSequence += 1;
1707
- return `${runtimeId}:${Date.now()}:${webuiCommandSequence}`;
1708
- }
1709
1988
  function settlePendingWebuiCommandAck(requestId, handled) {
1710
1989
  const pending = pendingWebuiCommandAcks.get(requestId);
1711
1990
  if (!pending) return;
@@ -1763,73 +2042,20 @@ function schedulePendingWebuiFlush(entry) {
1763
2042
  void flushPendingWebuiCommands(entry);
1764
2043
  }, 250);
1765
2044
  }
1766
- async function isWebuiCommandBridgeReady(entry) {
1767
- if (webuiViews.get(entry.runtimeId) !== entry || !entry.url) return false;
1768
- return entry.bridgeReady;
1769
- }
1770
- function queueWebuiCommand(entry, command) {
1771
- entry.pendingCommands.push(command);
1772
- if (entry.pendingCommands.length > MAX_PENDING_WEBUI_COMMANDS) {
1773
- entry.pendingCommands.splice(0, entry.pendingCommands.length - MAX_PENDING_WEBUI_COMMANDS);
2045
+ async function openProject(requestedRoot) {
2046
+ let projectRoot = requestedRoot;
2047
+ if (!projectRoot) {
2048
+ const result = await dialog.showOpenDialog({
2049
+ title: tMain("openProject"),
2050
+ properties: ["openDirectory"]
2051
+ });
2052
+ projectRoot = result.filePaths[0];
1774
2053
  }
1775
- entry.pendingFlushAttempts = 0;
1776
- setEntryWebuiStatus(entry, entry.status);
1777
- }
1778
- function getActiveWebuiEntry() {
1779
- const activeId = manager.snapshot().activeRuntimeId;
1780
- return activeId ? webuiViews.get(activeId) : void 0;
1781
- }
1782
- function attachWebuiEntry(entry) {
1783
- if (!mainWindow) return;
1784
- if (entry.attached) return;
1785
- mainWindow.contentView.addChildView(entry.view);
1786
- entry.attached = true;
1787
- }
1788
- function pruneWebuiEntries(runtimeIds) {
1789
- const live = new Set(runtimeIds);
1790
- for (const [id, entry] of webuiViews) {
1791
- if (!live.has(id)) {
1792
- disposeWebuiEntry(entry);
1793
- }
1794
- }
1795
- }
1796
- function disposeWebuiEntry(entry) {
1797
- webuiViews.delete(entry.runtimeId);
1798
- entry.pendingCommands.length = 0;
1799
- settlePendingWebuiCommandAcksForRuntime(entry.runtimeId, false);
1800
- if (entry.pendingFlushTimer) {
1801
- clearTimeout(entry.pendingFlushTimer);
1802
- entry.pendingFlushTimer = null;
1803
- }
1804
- if (mainWindow && entry.attached) {
1805
- mainWindow.contentView.removeChildView(entry.view);
1806
- }
1807
- entry.attached = false;
1808
- if (!entry.view.webContents.isDestroyed()) {
1809
- entry.view.webContents.close();
1810
- }
1811
- if (activeWebuiRuntimeId === entry.runtimeId) activeWebuiRuntimeId = null;
1812
- }
1813
- function disposeAllWebuiEntries() {
1814
- for (const entry of Array.from(webuiViews.values())) {
1815
- disposeWebuiEntry(entry);
1816
- }
1817
- webuiViews.clear();
1818
- }
1819
- async function openProject(requestedRoot) {
1820
- let projectRoot = requestedRoot;
1821
- if (!projectRoot) {
1822
- const result = await dialog.showOpenDialog({
1823
- title: tMain("openProject"),
1824
- properties: ["openDirectory"]
1825
- });
1826
- projectRoot = result.filePaths[0];
1827
- }
1828
- if (!projectRoot) return manager.snapshot();
1829
- await manager.openProject(projectRoot);
1830
- syncActiveWebuiView();
1831
- broadcastState();
1832
- return manager.snapshot();
2054
+ if (!projectRoot) return manager.snapshot();
2055
+ await manager.openProject(projectRoot);
2056
+ syncActiveWebuiView();
2057
+ broadcastState();
2058
+ return manager.snapshot();
1833
2059
  }
1834
2060
  async function registerProject(requestedRoot) {
1835
2061
  let projectRoot = requestedRoot;
@@ -1899,395 +2125,254 @@ async function restoreLastWorkspace() {
1899
2125
  syncActiveWebuiView();
1900
2126
  broadcastState();
1901
2127
  }
1902
- function activeRuntimeId() {
1903
- return manager.snapshot().activeRuntimeId;
1904
- }
1905
- function buildProjectsMenu(runtimes, actions) {
1906
- const projectGroups = groupProjectRuntimesForMenu(runtimes);
1907
- const menu = [
1908
- {
1909
- label: tMain("openProjectEllipsis"),
1910
- accelerator: "CmdOrCtrl+O",
1911
- click: () => void openProject()
1912
- },
1913
- { label: tMain("registerProjectEllipsis"), click: () => void registerProject() },
1914
- { type: "separator" }
1915
- ];
1916
- if (projectGroups.length === 0) {
1917
- menu.push({ label: tMain("noOpenProjectSessions"), enabled: false });
1918
- return menu;
1919
- }
1920
- for (const group of projectGroups) {
1921
- menu.push({
1922
- label: group.name,
1923
- submenu: [
1924
- {
1925
- label: tMain("newSession"),
1926
- click: () => actions.newSession(group.sessions[0]?.id ?? ""),
1927
- enabled: Boolean(group.sessions[0])
1928
- },
1929
- {
1930
- label: tMain("revealProjectFolder"),
1931
- click: () => actions.reveal(group.sessions[0]?.id ?? ""),
1932
- enabled: Boolean(group.sessions[0])
1933
- },
1934
- { type: "separator" },
1935
- ...group.sessions.map((runtime, index) => buildSessionMenu(runtime, index + 1, actions))
1936
- ]
1937
- });
1938
- }
1939
- return menu;
1940
- }
1941
- function buildSessionMenu(runtime, index, actions) {
1942
- const running = runtime.status === "running";
1943
- const label = `${tMain("session")} ${index} \xB7 ${runtime.status}`;
2128
+ function createMenuContext() {
1944
2129
  return {
1945
- label,
1946
- submenu: [
1947
- {
1948
- label: tMain("quickView"),
1949
- click: () => actions.activate(runtime.id)
1950
- },
1951
- {
1952
- label: "WebUI",
1953
- enabled: running,
1954
- submenu: [
1955
- {
1956
- label: tMain("chat"),
1957
- click: () => actions.activateAndNavigate(runtime.id, { activity: "chat", view: "chat" })
1958
- },
1959
- {
1960
- label: tMain("focusPrompt"),
1961
- click: () => actions.activateAndNavigate(runtime.id, { action: "focus-chat" })
1962
- },
1963
- {
1964
- label: tMain("terminal"),
1965
- click: () => actions.activateAndNavigate(runtime.id, { terminal: "toggle" })
1966
- },
1967
- {
1968
- label: tMain("newTerminal"),
1969
- click: () => actions.activateAndNavigate(runtime.id, { terminal: "new" })
1970
- },
1971
- { type: "separator" },
1972
- {
1973
- label: tMain("files"),
1974
- click: () => actions.activateAndNavigate(runtime.id, { activity: "files", view: "files" })
1975
- },
1976
- {
1977
- label: tMain("changes"),
1978
- click: () => actions.activateAndNavigate(runtime.id, { activity: "changes", view: "changes" })
1979
- },
1980
- {
1981
- label: tMain("sessions"),
1982
- click: () => actions.activateAndNavigate(runtime.id, { view: "sessions" })
1983
- },
1984
- {
1985
- label: tMain("fleetHQ"),
1986
- click: () => actions.activateAndNavigate(runtime.id, { activity: "officemap", view: "officemap" })
1987
- },
1988
- {
1989
- label: tMain("settings"),
1990
- click: () => actions.activateAndNavigate(runtime.id, { view: "settings" })
1991
- },
1992
- { type: "separator" },
1993
- {
1994
- label: tMain("commandPalette"),
1995
- click: () => actions.activateAndNavigate(runtime.id, { action: "open-command-palette" })
1996
- },
1997
- {
1998
- label: tMain("modelSwitcher"),
1999
- click: () => actions.activateAndNavigate(runtime.id, { action: "open-model-switcher" })
2000
- }
2001
- ]
2002
- },
2003
- { type: "separator" },
2004
- {
2005
- label: tMain("openInBrowser"),
2006
- enabled: running,
2007
- click: () => actions.openBrowser(runtime.id)
2008
- },
2009
- {
2010
- label: tMain("reloadWebui"),
2011
- enabled: running,
2012
- click: () => actions.reload(runtime.id)
2013
- },
2014
- {
2015
- label: tMain("closeSession"),
2016
- click: () => actions.close(runtime.id)
2017
- }
2018
- ]
2130
+ getSnapshot: () => manager.snapshot(),
2131
+ getActiveRuntime: () => {
2132
+ const snapshot = manager.snapshot();
2133
+ return snapshot.runtimes.find((r) => r.id === snapshot.activeRuntimeId);
2134
+ },
2135
+ getActiveWebuiPrefs: () => {
2136
+ const snapshot = manager.snapshot();
2137
+ if (!snapshot.activeRuntimeId) return void 0;
2138
+ return webuiViews.get(snapshot.activeRuntimeId)?.status.prefs;
2139
+ },
2140
+ getShellSidebarCollapsed: () => shellSidebarCollapsed,
2141
+ t: tMain,
2142
+ getRuntimeManager: () => manager,
2143
+ getWebuiViews: () => webuiViews,
2144
+ dispatchWebuiCommand,
2145
+ reloadActiveWebuiView,
2146
+ activateRuntime: async (id) => {
2147
+ await activateRuntime(id);
2148
+ },
2149
+ openProject: async () => {
2150
+ await openProject();
2151
+ },
2152
+ registerProject: async () => {
2153
+ await registerProject();
2154
+ },
2155
+ openSettings: async () => {
2156
+ await openSettings();
2157
+ },
2158
+ openProjectSession: async (id) => {
2159
+ await openProjectSession(id);
2160
+ },
2161
+ closeRuntime: async (id) => {
2162
+ await closeRuntime(id);
2163
+ },
2164
+ unregisterProject: async (root) => {
2165
+ await unregisterProject(root);
2166
+ },
2167
+ getActiveRuntimeId: () => activeWebuiRuntimeId,
2168
+ setShellSidebarCollapsed: (collapsed) => {
2169
+ setShellSidebarCollapsed(collapsed);
2170
+ },
2171
+ restoreLastWorkspace: async () => {
2172
+ await restoreLastWorkspace();
2173
+ },
2174
+ openExternal: (url) => {
2175
+ shell.openExternal(url);
2176
+ },
2177
+ revealInExplorer: (root) => {
2178
+ void shell.openPath(root);
2179
+ }
2019
2180
  };
2020
2181
  }
2021
- function groupProjectRuntimesForMenu(runtimes) {
2022
- const groups = /* @__PURE__ */ new Map();
2023
- for (const runtime of runtimes) {
2024
- if (runtime.kind !== "project") continue;
2025
- const key = normalizeMenuRoot(runtime.root);
2026
- const existing = groups.get(key);
2027
- if (existing) {
2028
- existing.sessions.push(runtime);
2029
- continue;
2182
+ function configureApplicationMenu2() {
2183
+ configureApplicationMenu(createMenuContext());
2184
+ }
2185
+ function registerIpcHandlers() {
2186
+ ipcMain.handle(IPC.getState, () => manager.snapshot());
2187
+ ipcMain.handle(
2188
+ IPC.getConversation,
2189
+ (_event, runtimeId) => bridge.snapshot(runtimeId)
2190
+ );
2191
+ ipcMain.handle(IPC.getWebuiStatus, () => webuiStatus);
2192
+ ipcMain.handle(
2193
+ IPC.navigateWebui,
2194
+ async (_event, command) => dispatchWebuiCommand(command)
2195
+ );
2196
+ ipcMain.handle(IPC.reloadWebui, async () => reloadActiveWebuiView());
2197
+ ipcMain.handle(IPC.setShellSidebarCollapsed, (_event, collapsed) => {
2198
+ setShellSidebarCollapsed(collapsed === true);
2199
+ return true;
2200
+ });
2201
+ ipcMain.handle(IPC.openSettings, async () => openSettings());
2202
+ ipcMain.handle(
2203
+ IPC.openProjectSession,
2204
+ async (_event, runtimeId) => openProjectSession(runtimeId)
2205
+ );
2206
+ ipcMain.handle(
2207
+ IPC.openProject,
2208
+ async (_event, requestedRoot) => openProject(requestedRoot)
2209
+ );
2210
+ ipcMain.handle(
2211
+ IPC.registerProject,
2212
+ async (_event, requestedRoot) => registerProject(requestedRoot)
2213
+ );
2214
+ ipcMain.handle(IPC.unregisterProject, async (_event, root) => unregisterProject(root));
2215
+ ipcMain.handle(IPC.activateRuntime, async (_event, id) => activateRuntime(id));
2216
+ ipcMain.handle(IPC.closeRuntime, async (_event, id) => closeRuntime(id));
2217
+ ipcMain.handle(
2218
+ IPC.sendMessage,
2219
+ async (_event, id, content) => bridge.sendMessage(id, runtimeWsUrlOrThrow(id), content)
2220
+ );
2221
+ ipcMain.handle(
2222
+ IPC.abortRuntime,
2223
+ async (_event, id) => bridge.abort(id, runtimeWsUrlOrThrow(id))
2224
+ );
2225
+ ipcMain.handle(IPC.openRuntimeInBrowser, async (_event, id) => {
2226
+ const url = manager.getRuntimeUrlWithToken(id);
2227
+ if (url) safeOpenExternal(url);
2228
+ });
2229
+ ipcMain.handle(IPC.revealRuntimeRoot, async (_event, id) => {
2230
+ const runtime = manager.getRuntime(id);
2231
+ if (runtime) void shell.openPath(runtime.root);
2232
+ });
2233
+ ipcMain.on(IPC.webuiReadyChanged, (event, ready) => {
2234
+ const entry = findWebuiEntryBySenderId(event.sender.id);
2235
+ if (!entry) return;
2236
+ entry.bridgeReady = ready === true;
2237
+ if (entry.bridgeReady) {
2238
+ setEntryWebuiStatus(entry, { ...entry.status, status: "ready" });
2239
+ schedulePendingWebuiFlush(entry);
2240
+ } else if (entry.status.status === "ready") {
2241
+ setEntryWebuiStatus(entry, { ...entry.status, status: "loading" });
2030
2242
  }
2031
- groups.set(key, {
2032
- key,
2033
- name: path3.basename(runtime.root) || runtime.name,
2034
- root: runtime.root,
2035
- sessions: [runtime]
2243
+ });
2244
+ ipcMain.on(IPC.webuiPrefsChanged, (event, prefs) => {
2245
+ const entry = findWebuiEntryBySenderId(event.sender.id);
2246
+ if (!entry) return;
2247
+ const next = {};
2248
+ if (isRecord2(prefs) && typeof prefs["yolo"] === "boolean") next.yolo = prefs["yolo"];
2249
+ if (isRecord2(prefs) && typeof prefs["nextPrediction"] === "boolean") {
2250
+ next.nextPrediction = prefs["nextPrediction"];
2251
+ }
2252
+ if (isRecord2(prefs) && typeof prefs["contextAutoCompact"] === "boolean") {
2253
+ next.contextAutoCompact = prefs["contextAutoCompact"];
2254
+ }
2255
+ if (Object.keys(next).length === 0) return;
2256
+ setEntryWebuiStatus(entry, {
2257
+ ...entry.status,
2258
+ prefs: { ...entry.status.prefs ?? {}, ...next }
2036
2259
  });
2037
- }
2038
- return [...groups.values()].sort((a, b) => a.name.localeCompare(b.name));
2039
- }
2040
- function normalizeMenuRoot(root) {
2041
- return path3.resolve(root).replace(/\\/g, "/").replace(/\/+$/g, "").toLowerCase();
2260
+ });
2261
+ ipcMain.on(
2262
+ IPC.webuiCommandAck,
2263
+ (event, requestId, handled, _message) => {
2264
+ const entry = findWebuiEntryBySenderId(event.sender.id);
2265
+ if (!entry || typeof requestId !== "string") return;
2266
+ const pending = pendingWebuiCommandAcks.get(requestId);
2267
+ if (!pending || pending.runtimeId !== entry.runtimeId) return;
2268
+ settlePendingWebuiCommandAck(requestId, handled === true);
2269
+ }
2270
+ );
2271
+ ipcMain.on(IPC.setLocale, (_event, locale) => {
2272
+ setMainLocale(locale);
2273
+ configureApplicationMenu2();
2274
+ broadcastLocaleToEmbeddedWebuis(locale);
2275
+ void writeUiLocale(locale);
2276
+ });
2042
2277
  }
2043
- function configureApplicationMenu() {
2044
- const navigate = (command) => {
2045
- void dispatchWebuiCommand(command);
2046
- };
2047
- const activateAndNavigate = (runtimeId, command) => {
2048
- void activateRuntime(runtimeId).then(() => dispatchWebuiCommand(command));
2049
- };
2050
- const reloadRuntimeWebui = (runtimeId) => {
2051
- void activateRuntime(runtimeId).then(() => reloadActiveWebuiView());
2278
+ async function boot() {
2279
+ const locale = await readUiLocale();
2280
+ if (locale) setMainLocale(locale);
2281
+ const shellUrl = rendererIndexPath();
2282
+ shellView = new WebContentsView({
2283
+ webPreferences: {
2284
+ preload: preloadPath(),
2285
+ contextIsolation: true,
2286
+ nodeIntegration: false,
2287
+ sandbox: false
2288
+ }
2289
+ });
2290
+ shellView.webContents.setWindowOpenHandler(({ url }) => {
2291
+ safeOpenExternal(url);
2292
+ return { action: "deny" };
2293
+ });
2294
+ await shellView.webContents.loadURL(shellUrl);
2295
+ const prevState = validatedWindowState(manager.getWindowState());
2296
+ const defaultWidth = 1180;
2297
+ const defaultHeight = 720;
2298
+ const winOptions = {
2299
+ width: prevState?.width ?? defaultWidth,
2300
+ height: prevState?.height ?? defaultHeight,
2301
+ show: false,
2302
+ minWidth: MIN_WINDOW_WIDTH2,
2303
+ minHeight: MIN_WINDOW_HEIGHT2,
2304
+ title: tMain("windowTitle")
2052
2305
  };
2053
- const snapshot = manager.snapshot();
2054
- const active = snapshot.runtimes.find((runtime) => runtime.id === snapshot.activeRuntimeId);
2055
- const hasActiveRuntime = Boolean(active);
2056
- const hasActiveWebui = active?.status === "running";
2057
- const hasActiveProjectWebui = hasActiveWebui && active?.kind === "project";
2058
- const activeWebuiPrefs = active ? webuiViews.get(active.id)?.status.prefs : void 0;
2059
- const yoloChecked = activeWebuiPrefs?.yolo === true;
2060
- const nextPredictionChecked = activeWebuiPrefs?.nextPrediction === true;
2061
- const contextAutoCompactChecked = activeWebuiPrefs?.contextAutoCompact === true;
2062
- const webuiItem = (item) => ({
2063
- ...item,
2064
- enabled: item.enabled ?? hasActiveWebui
2306
+ if (prevState) {
2307
+ if (prevState.x !== void 0) winOptions.x = prevState.x;
2308
+ if (prevState.y !== void 0) winOptions.y = prevState.y;
2309
+ }
2310
+ mainWindow = new BaseWindow(winOptions);
2311
+ mainWindow.on("resized", scheduleWindowStateSave);
2312
+ mainWindow.on("moved", scheduleWindowStateSave);
2313
+ mainWindow.on("maximize", scheduleWindowStateSave);
2314
+ mainWindow.on("unmaximize", scheduleWindowStateSave);
2315
+ if (prevState?.maximized) {
2316
+ mainWindow.maximize();
2317
+ }
2318
+ mainWindow.contentView.addChildView(shellView);
2319
+ layoutViews();
2320
+ registerIpcHandlers();
2321
+ configureApplicationMenu2();
2322
+ mainWindow.on("resize", layoutViews);
2323
+ bridge.on("changed", (conversation) => {
2324
+ if (!shellView || shellView.webContents.isDestroyed()) return;
2325
+ shellView.webContents.send(IPC.conversationChanged, conversation);
2065
2326
  });
2066
- const template = [
2067
- {
2068
- label: tMain("file"),
2069
- submenu: [
2070
- {
2071
- label: tMain("openProjectEllipsis"),
2072
- accelerator: "CmdOrCtrl+O",
2073
- click: () => void openProject()
2074
- },
2075
- { label: tMain("registerProjectEllipsis"), click: () => void registerProject() },
2076
- {
2077
- label: tMain("removeActiveFromRegistry"),
2078
- enabled: hasActiveProjectWebui,
2079
- click: () => {
2080
- if (active?.kind === "project") void unregisterProject(active.root);
2081
- }
2082
- },
2083
- { type: "separator" },
2084
- {
2085
- label: tMain("newSessionForActive"),
2086
- accelerator: "CmdOrCtrl+N",
2087
- enabled: hasActiveProjectWebui,
2088
- click: () => void openProjectSession(active?.id)
2089
- },
2090
- {
2091
- label: tMain("settings"),
2092
- accelerator: "CmdOrCtrl+,",
2093
- click: () => {
2094
- if (hasActiveWebui) navigate({ view: "settings" });
2095
- else void openSettings();
2096
- }
2097
- },
2098
- { type: "separator" },
2099
- {
2100
- label: tMain("closeActiveRuntime"),
2101
- accelerator: "CmdOrCtrl+W",
2102
- enabled: hasActiveRuntime,
2103
- click: () => {
2104
- const id = activeRuntimeId();
2105
- if (id) void closeRuntime(id);
2106
- }
2107
- },
2108
- { type: "separator" },
2109
- { role: process.platform === "darwin" ? "close" : "quit" }
2110
- ]
2111
- },
2112
- {
2113
- label: tMain("projects"),
2114
- submenu: buildProjectsMenu(snapshot.runtimes, {
2115
- activate: (runtimeId) => void activateRuntime(runtimeId),
2116
- activateAndNavigate,
2117
- newSession: (runtimeId) => void openProjectSession(runtimeId),
2118
- openBrowser: (runtimeId) => {
2119
- const url = manager.getRuntimeUrlWithToken(runtimeId);
2120
- if (url) safeOpenExternal(url);
2121
- },
2122
- reload: reloadRuntimeWebui,
2123
- close: (runtimeId) => void closeRuntime(runtimeId),
2124
- reveal: (runtimeId) => {
2125
- const runtime = manager.getRuntime(runtimeId);
2126
- if (runtime) void shell.openPath(runtime.root);
2127
- }
2128
- })
2129
- },
2130
- {
2131
- label: tMain("workspace"),
2132
- submenu: [
2133
- webuiItem({
2134
- label: tMain("openChat"),
2135
- accelerator: "CmdOrCtrl+1",
2136
- click: () => navigate({ activity: "chat", view: "chat" })
2137
- }),
2138
- webuiItem({
2139
- label: tMain("focusPrompt"),
2140
- accelerator: "CmdOrCtrl+/",
2141
- click: () => navigate({ action: "focus-chat" })
2142
- }),
2143
- webuiItem({
2144
- label: tMain("toggleTerminal"),
2145
- accelerator: "CmdOrCtrl+`",
2146
- click: () => navigate({ terminal: "toggle" })
2147
- }),
2148
- webuiItem({ label: tMain("newTerminal"), click: () => navigate({ terminal: "new" }) }),
2149
- { type: "separator" },
2150
- webuiItem({
2151
- label: tMain("commandPalette"),
2152
- accelerator: "CmdOrCtrl+K",
2153
- click: () => navigate({ action: "open-command-palette" })
2154
- }),
2155
- webuiItem({
2156
- label: tMain("quickModelSwitcher"),
2157
- accelerator: "CmdOrCtrl+M",
2158
- click: () => navigate({ action: "open-model-switcher" })
2159
- }),
2160
- webuiItem({
2161
- type: "checkbox",
2162
- label: tMain("yoloMode"),
2163
- checked: yoloChecked,
2164
- accelerator: "CmdOrCtrl+Shift+Y",
2165
- click: () => navigate({ pref: { key: "yolo", toggle: true } })
2166
- }),
2167
- webuiItem({
2168
- type: "checkbox",
2169
- label: tMain("nextPrediction"),
2170
- checked: nextPredictionChecked,
2171
- click: () => navigate({ pref: { key: "nextPrediction", toggle: true } })
2172
- }),
2173
- webuiItem({
2174
- type: "checkbox",
2175
- label: tMain("contextAutoCompact"),
2176
- checked: contextAutoCompactChecked,
2177
- click: () => navigate({ pref: { key: "contextAutoCompact", toggle: true } })
2178
- }),
2179
- { type: "separator" },
2180
- webuiItem({
2181
- label: tMain("reloadActiveWebui"),
2182
- accelerator: "CmdOrCtrl+Shift+R",
2183
- click: () => void reloadActiveWebuiView()
2184
- })
2185
- ]
2186
- },
2187
- {
2188
- label: tMain("view"),
2189
- submenu: [
2190
- {
2191
- type: "checkbox",
2192
- label: tMain("compactDesktopSidebar"),
2193
- accelerator: "CmdOrCtrl+B",
2194
- checked: shellSidebarCollapsed,
2195
- click: () => setShellSidebarCollapsed(!shellSidebarCollapsed)
2196
- },
2197
- { type: "separator" },
2198
- { role: "reload" },
2199
- { role: "toggleDevTools" },
2200
- { type: "separator" },
2201
- { role: "resetZoom" },
2202
- { role: "zoomIn" },
2203
- { role: "zoomOut" },
2204
- { type: "separator" },
2205
- { role: "togglefullscreen" }
2206
- ]
2327
+ manager.on("changed", () => {
2328
+ syncActiveWebuiView();
2329
+ configureApplicationMenu2();
2330
+ broadcastState();
2331
+ });
2332
+ let lastWatchedLocale;
2333
+ watchProviderConfig(
2334
+ desktopConfigPaths.globalConfigPath,
2335
+ desktopConfigPaths.vault,
2336
+ (snapshot) => {
2337
+ const updated = snapshot.uiLocale;
2338
+ if (!updated || updated === lastWatchedLocale) return;
2339
+ lastWatchedLocale = updated;
2340
+ setMainLocale(updated);
2341
+ configureApplicationMenu2();
2342
+ broadcastLocaleToEmbeddedWebuis(updated);
2343
+ if (shellView && !shellView.webContents.isDestroyed()) {
2344
+ shellView.webContents.send(IPC.localeChanged, updated);
2345
+ }
2207
2346
  }
2208
- ];
2209
- Menu.setApplicationMenu(Menu.buildFromTemplate(template));
2347
+ );
2348
+ mainWindow.on("close", (event) => {
2349
+ if (quittingAfterCleanup) return;
2350
+ event.preventDefault();
2351
+ bridge.closeAll();
2352
+ disposeAllWebuiEntries();
2353
+ void saveWindowState();
2354
+ quittingAfterCleanup = true;
2355
+ app.exit(0);
2356
+ });
2357
+ await restoreLastWorkspace();
2358
+ mainWindow.show();
2359
+ shellView.webContents.focus();
2210
2360
  }
2211
- manager.on("changed", () => {
2212
- configureApplicationMenu();
2213
- syncActiveWebuiView();
2214
- broadcastState();
2215
- });
2216
- ipcMain.on(IPC.setLocale, (_event, locale) => {
2217
- setMainLocale(locale);
2218
- configureApplicationMenu();
2219
- broadcastLocaleToEmbeddedWebuis(locale);
2220
- void writeUiLocale(locale);
2221
- });
2222
- watchProviderConfig(
2223
- desktopConfigPaths.globalConfigPath,
2224
- desktopConfigPaths.vault,
2225
- (snapshot) => {
2226
- if (snapshot.uiLocale === void 0) return;
2227
- setMainLocale(snapshot.uiLocale);
2228
- configureApplicationMenu();
2229
- shellView?.webContents.send(IPC.localeChanged, snapshot.uiLocale);
2230
- broadcastLocaleToEmbeddedWebuis(snapshot.uiLocale);
2231
- },
2232
- { warn: (m) => console.warn(`Config watcher: ${m}`) }
2233
- );
2234
- bridge.on("changed", (conversation) => {
2235
- shellView?.webContents.send(IPC.conversationChanged, conversation);
2236
- });
2361
+ app.whenReady().then(boot);
2237
2362
  app.on("window-all-closed", () => {
2238
- if (process.platform !== "darwin") app.quit();
2363
+ app.quit();
2239
2364
  });
2240
- app.on("before-quit", (event) => {
2241
- if (quittingAfterCleanup) return;
2242
- event.preventDefault();
2243
- quittingAfterCleanup = true;
2244
- if (saveWindowStateTimer) {
2245
- clearTimeout(saveWindowStateTimer);
2246
- saveWindowStateTimer = null;
2365
+ app.on("before-quit", () => {
2366
+ if (mainWindow) {
2367
+ mainWindow.removeAllListeners("close");
2368
+ void saveWindowState();
2247
2369
  }
2248
2370
  bridge.closeAll();
2249
- void saveWindowState().catch(() => void 0).finally(() => manager.closeAll({ persistWorkspace: false }).finally(() => app.quit()));
2371
+ disposeAllWebuiEntries();
2250
2372
  });
2251
- app.whenReady().then(async () => {
2252
- registerIpc();
2253
- await createWindow();
2254
- app.on("activate", () => {
2255
- if (mainWindow === null) void createWindow();
2256
- });
2257
- }).catch((err) => {
2258
- console.error(err);
2259
- app.exit(1);
2373
+ app.on("activate", () => {
2374
+ if (!mainWindow) return;
2375
+ mainWindow.show();
2376
+ shellView?.webContents.focus();
2260
2377
  });
2261
- function sameOrigin(candidate, base) {
2262
- if (!base) return false;
2263
- try {
2264
- const candidateUrl = new URL(candidate);
2265
- const baseUrl = new URL(base);
2266
- return candidateUrl.origin === baseUrl.origin;
2267
- } catch {
2268
- return false;
2269
- }
2270
- }
2271
- function desktopSettingsWorkspaceRoot() {
2272
- return path3.join(wstackGlobalRoot3(), "desktop", "global-settings-workspace");
2273
- }
2274
- function validatedWindowState(state) {
2275
- if (!state) return null;
2276
- if (state.width < MIN_WINDOW_WIDTH2 || state.height < MIN_WINDOW_HEIGHT2) return null;
2277
- if (state.x === void 0 || state.y === void 0) return state;
2278
- const candidate = {
2279
- x: state.x,
2280
- y: state.y,
2281
- width: state.width,
2282
- height: state.height
2283
- };
2284
- const visibleOnSomeDisplay = screen.getAllDisplays().some((display) => {
2285
- const area = display.workArea;
2286
- return rectanglesIntersect(candidate, area);
2287
- });
2288
- return visibleOnSomeDisplay ? state : null;
2289
- }
2290
- function rectanglesIntersect(left, right) {
2291
- return left.x < right.x + right.width && left.x + left.width > right.x && left.y < right.y + right.height && left.y + left.height > right.y;
2292
- }
2293
2378
  //# sourceMappingURL=main.js.map