@wrongstack/desktop 0.287.0 → 0.291.0

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
@@ -3,7 +3,7 @@ import * as path4 from "node:path";
3
3
  import * as fs3 from "node:fs/promises";
4
4
  import { wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core/utils";
5
5
  import {
6
- app,
6
+ app as app2,
7
7
  BaseWindow,
8
8
  dialog,
9
9
  ipcMain as ipcMain2,
@@ -13,6 +13,52 @@ import {
13
13
  WebContentsView
14
14
  } from "electron";
15
15
 
16
+ // src/main/macos-platform.ts
17
+ import { app, Menu } from "electron";
18
+ function initMacOS() {
19
+ if (process.platform !== "darwin") return;
20
+ app.setActivationPolicy("regular");
21
+ app.on("open-file", (_event, filePath) => {
22
+ if (app.isReady()) {
23
+ handleFileOpen(filePath);
24
+ }
25
+ });
26
+ app.dock?.setMenu(
27
+ Menu.buildFromTemplate([
28
+ {
29
+ label: "New Window",
30
+ click: () => {
31
+ }
32
+ }
33
+ ])
34
+ );
35
+ app.setAboutPanelOptions({
36
+ applicationName: "WrongStack Desktop",
37
+ applicationVersion: app.getVersion(),
38
+ version: process.versions.electron ? `Electron ${process.versions.electron}` : ""
39
+ });
40
+ }
41
+ function handleFileOpen(filePath) {
42
+ pendingOpenFilePath = filePath;
43
+ }
44
+ var pendingOpenFilePath = null;
45
+ function drainPendingOpenFilePath() {
46
+ const path5 = pendingOpenFilePath;
47
+ pendingOpenFilePath = null;
48
+ return path5;
49
+ }
50
+ function firstOpenFileArg(argv) {
51
+ if (process.platform !== "darwin") return null;
52
+ for (let index = 1; index < argv.length; index++) {
53
+ const arg = argv[index];
54
+ if (arg == null) continue;
55
+ if (arg.startsWith("-")) continue;
56
+ if (arg === "." || arg === "--") continue;
57
+ return arg;
58
+ }
59
+ return null;
60
+ }
61
+
16
62
  // src/main/agent-bridge.ts
17
63
  import { randomUUID } from "node:crypto";
18
64
  import { EventEmitter } from "node:events";
@@ -398,7 +444,11 @@ var IPC = {
398
444
  // Embedded WebUI view side — the desktop shell pushes locale changes here
399
445
  // so the React WebUI inside Electron can swap i18n instantly, without waiting
400
446
  // for the config-file watcher → WS prefs.updated round-trip.
401
- webuiLocaleChanged: "desktop:webui-locale-changed"
447
+ webuiLocaleChanged: "desktop:webui-locale-changed",
448
+ // macOS open-file event forwarded from main process to shell renderer.
449
+ // The shell uses this to decide whether to open a dragged/double-clicked
450
+ // path as a project directory.
451
+ openFile: "desktop:open-file"
402
452
  };
403
453
 
404
454
  // src/main/i18n-main.ts
@@ -753,8 +803,8 @@ async function writeUiLocale(code) {
753
803
  import { spawn } from "node:child_process";
754
804
  import { randomBytes } from "node:crypto";
755
805
  import { EventEmitter as EventEmitter2 } from "node:events";
756
- import * as fs2 from "node:fs/promises";
757
806
  import { existsSync } from "node:fs";
807
+ import * as fs2 from "node:fs/promises";
758
808
  import * as http from "node:http";
759
809
  import { createRequire } from "node:module";
760
810
  import * as net from "node:net";
@@ -1183,10 +1233,42 @@ var DesktopRuntimeManager = class extends EventEmitter2 {
1183
1233
  }, 250);
1184
1234
  }
1185
1235
  };
1236
+ function hasChildExited(child) {
1237
+ return child.exitCode !== null || child.signalCode !== null;
1238
+ }
1239
+ function waitForChildExit(child, timeoutMs) {
1240
+ if (hasChildExited(child)) return Promise.resolve(true);
1241
+ return new Promise((resolve2) => {
1242
+ let settled = false;
1243
+ const finish = (exited) => {
1244
+ if (settled) return;
1245
+ settled = true;
1246
+ clearTimeout(timer);
1247
+ child.off("exit", onExit);
1248
+ resolve2(exited);
1249
+ };
1250
+ const onExit = () => finish(true);
1251
+ const timer = setTimeout(() => finish(hasChildExited(child)), timeoutMs);
1252
+ timer.unref?.();
1253
+ child.once("exit", onExit);
1254
+ if (hasChildExited(child)) finish(true);
1255
+ });
1256
+ }
1186
1257
  async function terminateProcessTree(child) {
1187
- if (!child || child.killed || !child.pid) return;
1258
+ if (!child?.pid || hasChildExited(child)) return;
1188
1259
  if (process.platform !== "win32") {
1260
+ const pid = child.pid;
1261
+ const exited = waitForChildExit(child, 5e3);
1189
1262
  child.kill("SIGTERM");
1263
+ if (await exited) return;
1264
+ if (!hasChildExited(child)) {
1265
+ try {
1266
+ process.kill(-pid, "SIGKILL");
1267
+ } catch {
1268
+ child.kill("SIGKILL");
1269
+ }
1270
+ await waitForChildExit(child, 1e3);
1271
+ }
1190
1272
  return;
1191
1273
  }
1192
1274
  await new Promise((resolve2) => {
@@ -1471,10 +1553,14 @@ async function touchGlobalProjectManifest(entry) {
1471
1553
  } else {
1472
1554
  projects.push({ ...entry, createdAt: entry.lastSeen });
1473
1555
  }
1474
- const sorted = projects.sort((a, b) => (b.lastSeen ?? b.createdAt ?? "").localeCompare(a.lastSeen ?? a.createdAt ?? "")).slice(0, 80);
1556
+ const sorted = projects.sort(
1557
+ (a, b) => (b.lastSeen ?? b.createdAt ?? "").localeCompare(a.lastSeen ?? a.createdAt ?? "")
1558
+ ).slice(0, 80);
1475
1559
  await fs2.mkdir(path2.dirname(manifestFile), { recursive: true });
1476
1560
  await atomicWrite2(manifestFile, `${JSON.stringify({ projects: sorted }, null, 2)}
1477
- `, { mode: 384 });
1561
+ `, {
1562
+ mode: 384
1563
+ });
1478
1564
  return sorted;
1479
1565
  }
1480
1566
  async function removeGlobalProjectManifest(projectRoot) {
@@ -1526,10 +1612,8 @@ var DESKTOP_WEBUI_ACTIONS = /* @__PURE__ */ new Set([
1526
1612
  var DESKTOP_WEBUI_VIEWS = /* @__PURE__ */ new Set([
1527
1613
  "chat",
1528
1614
  "settings",
1529
- "autophase",
1530
- "specs",
1531
- "sddboard",
1532
- "sddwizard",
1615
+ "goal",
1616
+ "sddhub",
1533
1617
  "files",
1534
1618
  "changes",
1535
1619
  "sessions",
@@ -1561,7 +1645,6 @@ var DESKTOP_WEBUI_OVERLAYS = /* @__PURE__ */ new Set([
1561
1645
  "queue"
1562
1646
  ]);
1563
1647
  var DESKTOP_WEBUI_DOCKS = /* @__PURE__ */ new Set([
1564
- "autophase",
1565
1648
  "goal",
1566
1649
  "fleet",
1567
1650
  "work",
@@ -1685,7 +1768,7 @@ function getSidebarWidth(windowWidth, collapsed) {
1685
1768
  }
1686
1769
 
1687
1770
  // src/main/menu/index.ts
1688
- import { Menu } from "electron";
1771
+ import { Menu as Menu2 } from "electron";
1689
1772
 
1690
1773
  // src/main/menu/projects-menu.ts
1691
1774
  import path3 from "node:path";
@@ -2018,7 +2101,7 @@ function configureApplicationMenu(ctx) {
2018
2101
  buildWorkspaceMenu(ctx, actions, hasActiveWebui, activeWebuiPrefs, navigate),
2019
2102
  buildViewMenu(ctx)
2020
2103
  ];
2021
- Menu.setApplicationMenu(Menu.buildFromTemplate(template));
2104
+ Menu2.setApplicationMenu(Menu2.buildFromTemplate(template));
2022
2105
  }
2023
2106
 
2024
2107
  // src/main/ipc-handlers/index.ts
@@ -2074,7 +2157,6 @@ import { z } from "zod";
2074
2157
  var RUNTIME_ID_PATTERN = /^[a-zA-Z0-9._:-]{3,120}$/;
2075
2158
  var runtimeIdSchema = z.string().regex(RUNTIME_ID_PATTERN, "Invalid runtime ID format");
2076
2159
  var pathSchema = z.string().min(1).max(1e4);
2077
- var nonEmptyStringSchema = z.string().min(1);
2078
2160
  var booleanSchema = z.boolean();
2079
2161
  var numberSchema = z.number().int().finite();
2080
2162
  var openProjectSchema = z.object({
@@ -2308,8 +2390,11 @@ function isRecord2(value) {
2308
2390
  }
2309
2391
 
2310
2392
  // src/main/main.ts
2311
- app.setAppUserModelId("com.wrongstack.desktop");
2312
- app.setPath("userData", path4.join(wstackGlobalRoot3(), "desktop", "electron-profile"));
2393
+ initMacOS();
2394
+ if (process.platform === "win32") {
2395
+ app2.setAppUserModelId("com.wrongstack.desktop");
2396
+ }
2397
+ app2.setPath("userData", path4.join(wstackGlobalRoot3(), "desktop", "electron-profile"));
2313
2398
  var manager = new DesktopRuntimeManager();
2314
2399
  var bridge = new DesktopAgentBridge();
2315
2400
  var mainWindow = null;
@@ -2341,6 +2426,22 @@ function sameOrigin(candidate, base) {
2341
2426
  return false;
2342
2427
  }
2343
2428
  }
2429
+ function revealInExplorer(root) {
2430
+ shell.openPath(root).catch((err) => {
2431
+ if (process.platform === "darwin") {
2432
+ void shell.openPath(path4.dirname(root)).catch(() => void 0);
2433
+ }
2434
+ console.error(
2435
+ JSON.stringify({
2436
+ level: "warn",
2437
+ event: "desktop.reveal_in_explorer_failed",
2438
+ root,
2439
+ message: err instanceof Error ? err.message : String(err),
2440
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2441
+ })
2442
+ );
2443
+ });
2444
+ }
2344
2445
  function setShellSidebarCollapsed(collapsed) {
2345
2446
  shellSidebarCollapsed = collapsed;
2346
2447
  layoutWebuiViews();
@@ -2885,7 +2986,7 @@ function createMenuContext() {
2885
2986
  shell.openExternal(url);
2886
2987
  },
2887
2988
  revealInExplorer: (root) => {
2888
- void shell.openPath(root);
2989
+ revealInExplorer(root);
2889
2990
  }
2890
2991
  };
2891
2992
  }
@@ -2922,7 +3023,7 @@ function buildIpcHandlerContext() {
2922
3023
  abortRuntime: (id, wsUrl) => bridge.abort(id, wsUrl),
2923
3024
  openExternal: (url) => safeOpenExternal(url),
2924
3025
  revealInExplorer: (root) => {
2925
- void shell.openPath(root);
3026
+ revealInExplorer(root);
2926
3027
  },
2927
3028
  findWebuiEntryBySenderId: (senderId) => findWebuiEntryBySenderId(senderId),
2928
3029
  getPendingWebuiCommandAcks: () => pendingWebuiCommandAcks,
@@ -2953,12 +3054,29 @@ async function boot() {
2953
3054
  const defaultWidth = 1180;
2954
3055
  const defaultHeight = 720;
2955
3056
  let appIcon;
2956
- try {
2957
- const iconPath = new URL("../../assets/icon.svg", import.meta.url).pathname;
2958
- await fs3.stat(iconPath);
2959
- appIcon = nativeImage.createFromPath(iconPath);
2960
- if (appIcon.isEmpty()) appIcon = void 0;
2961
- } catch {
3057
+ if (process.platform !== "darwin") {
3058
+ try {
3059
+ const iconPath = new URL("../../assets/icon.svg", import.meta.url).pathname;
3060
+ await fs3.stat(iconPath);
3061
+ appIcon = nativeImage.createFromPath(iconPath);
3062
+ if (appIcon.isEmpty()) appIcon = void 0;
3063
+ } catch {
3064
+ }
3065
+ } else {
3066
+ try {
3067
+ const pngPath = new URL("../../assets/icon.png", import.meta.url).pathname;
3068
+ await fs3.stat(pngPath);
3069
+ appIcon = nativeImage.createFromPath(pngPath);
3070
+ if (appIcon.isEmpty()) appIcon = void 0;
3071
+ } catch {
3072
+ try {
3073
+ const icnsPath = new URL("../../assets/icon.icns", import.meta.url).pathname;
3074
+ await fs3.stat(icnsPath);
3075
+ appIcon = nativeImage.createFromPath(icnsPath);
3076
+ if (appIcon.isEmpty()) appIcon = void 0;
3077
+ } catch {
3078
+ }
3079
+ }
2962
3080
  }
2963
3081
  const winOptions = {
2964
3082
  width: prevState?.width ?? defaultWidth,
@@ -2994,6 +3112,11 @@ async function boot() {
2994
3112
  configureApplicationMenu2();
2995
3113
  broadcastState();
2996
3114
  });
3115
+ app2.on("open-file", (_event, filePath) => {
3116
+ if (shellView && !shellView.webContents.isDestroyed()) {
3117
+ shellView.webContents.send(IPC.openFile, filePath);
3118
+ }
3119
+ });
2997
3120
  let lastWatchedLocale;
2998
3121
  watchProviderConfig(
2999
3122
  desktopConfigPaths.globalConfigPath,
@@ -3017,17 +3140,25 @@ async function boot() {
3017
3140
  disposeAllWebuiEntries();
3018
3141
  void saveWindowState();
3019
3142
  quittingAfterCleanup = true;
3020
- app.exit(0);
3143
+ app2.exit(0);
3021
3144
  });
3022
3145
  await restoreLastWorkspace();
3146
+ const argvOpenPath = firstOpenFileArg(process.argv);
3147
+ const queuedOpenPath = drainPendingOpenFilePath();
3148
+ const openPath = argvOpenPath ?? queuedOpenPath;
3149
+ if (openPath && shellView && !shellView.webContents.isDestroyed()) {
3150
+ shellView.webContents.send(IPC.openFile, openPath);
3151
+ }
3023
3152
  mainWindow.show();
3024
3153
  shellView.webContents.focus();
3025
3154
  }
3026
- app.whenReady().then(boot);
3027
- app.on("window-all-closed", () => {
3028
- app.quit();
3155
+ app2.whenReady().then(boot);
3156
+ app2.on("window-all-closed", () => {
3157
+ if (process.platform !== "darwin") {
3158
+ app2.quit();
3159
+ }
3029
3160
  });
3030
- app.on("before-quit", () => {
3161
+ app2.on("before-quit", () => {
3031
3162
  if (mainWindow) {
3032
3163
  mainWindow.removeAllListeners("close");
3033
3164
  void saveWindowState();
@@ -3035,7 +3166,7 @@ app.on("before-quit", () => {
3035
3166
  bridge.closeAll();
3036
3167
  disposeAllWebuiEntries();
3037
3168
  });
3038
- app.on("activate", () => {
3169
+ app2.on("activate", () => {
3039
3170
  if (!mainWindow) return;
3040
3171
  mainWindow.show();
3041
3172
  shellView?.webContents.focus();