diff --git a/package.json b/package.json
index ecdb70b..309ebb1 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,6 @@
{
"name": "stratosort-core",
+ "productName": "StratoSort Core",
"version": "2.0.1",
"description": "StratoSort Core - Local AI Document Organizer. Privacy-focused document organization using in-process AI (node-llama-cpp + Orama). Zero external dependencies.",
"keywords": [
diff --git a/scripts/patch-electron-mac.js b/scripts/patch-electron-mac.js
new file mode 100644
index 0000000..f0509d4
--- /dev/null
+++ b/scripts/patch-electron-mac.js
@@ -0,0 +1,158 @@
+#!/usr/bin/env node
+/**
+ * Patch Electron.app Info.plist for macOS development
+ *
+ * During development, macOS reads the dock label and Activity Monitor process
+ * name from the Electron binary's Info.plist, which defaults to "Electron".
+ * This script patches CFBundleName and CFBundleDisplayName so the dock shows
+ * "StratoSort Core" instead.
+ *
+ * This is safe and idempotent — re-running produces the same result.
+ * Only affects node_modules (dev-only); production builds use electron-builder
+ * which sets these values correctly from electron-builder.json.
+ *
+ * Usage:
+ * node scripts/patch-electron-mac.js # Patch Info.plist
+ * node scripts/patch-electron-mac.js --check # Check current values (no changes)
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const APP_NAME = 'StratoSort Core';
+const BUNDLE_ID = 'com.stratosort.app';
+
+/**
+ * Locate the Electron.app Info.plist inside node_modules.
+ * Returns null if not found (e.g. running on non-macOS or Electron not installed).
+ */
+function findInfoPlist() {
+ const candidates = [
+ // Standard npm install location
+ path.join(
+ __dirname,
+ '..',
+ 'node_modules',
+ 'electron',
+ 'dist',
+ 'Electron.app',
+ 'Contents',
+ 'Info.plist'
+ ),
+ // Hoisted or alternative layouts
+ path.join(__dirname, '..', '..', 'electron', 'dist', 'Electron.app', 'Contents', 'Info.plist')
+ ];
+
+ for (const candidate of candidates) {
+ if (fs.existsSync(candidate)) {
+ return candidate;
+ }
+ }
+ return null;
+}
+
+/**
+ * Replace a plist string value.
+ * Matches: KEY\n\tOLD
+ * and replaces OLD with NEW.
+ */
+function replacePlistValue(content, key, newValue) {
+ // Match the key followed by a string value (handles various whitespace)
+ const regex = new RegExp(`(${escapeRegex(key)}\\s*)(.*?)()`, 'g');
+ return content.replace(regex, `$1${newValue}$3`);
+}
+
+function escapeRegex(str) {
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
+function readPlistValue(content, key) {
+ const regex = new RegExp(`${escapeRegex(key)}\\s*(.*?)`);
+ const match = content.match(regex);
+ return match ? match[1] : null;
+}
+
+function main() {
+ if (process.platform !== 'darwin') {
+ console.log('[patch-electron-mac] Skipped — not macOS');
+ return 0;
+ }
+
+ const plistPath = findInfoPlist();
+ if (!plistPath) {
+ console.log('[patch-electron-mac] Skipped — Electron.app not found');
+ return 0;
+ }
+
+ const content = fs.readFileSync(plistPath, 'utf8');
+
+ const currentName = readPlistValue(content, 'CFBundleName');
+ const currentDisplayName = readPlistValue(content, 'CFBundleDisplayName');
+ const currentBundleId = readPlistValue(content, 'CFBundleIdentifier');
+
+ // --check mode: report current values without modifying
+ if (process.argv.includes('--check')) {
+ console.log('[patch-electron-mac] Info.plist:', plistPath);
+ console.log(` CFBundleName: ${currentName}`);
+ console.log(` CFBundleDisplayName: ${currentDisplayName}`);
+ console.log(` CFBundleIdentifier: ${currentBundleId}`);
+
+ const isPatched = currentName === APP_NAME;
+ console.log(
+ ` Status: ${isPatched ? '✅ Patched' : '⚠️ Not patched (shows "Electron" in dock)'}`
+ );
+ return isPatched ? 0 : 1;
+ }
+
+ // Check if already patched
+ if (currentName === APP_NAME && currentDisplayName === APP_NAME) {
+ console.log('[patch-electron-mac] Already patched — dock will show "StratoSort Core"');
+ return 0;
+ }
+
+ // Apply patches
+ let patched = content;
+ patched = replacePlistValue(patched, 'CFBundleName', APP_NAME);
+
+ // CFBundleDisplayName may not exist in the original; add it if missing
+ if (currentDisplayName !== null) {
+ patched = replacePlistValue(patched, 'CFBundleDisplayName', APP_NAME);
+ } else {
+ // Insert after CFBundleName
+ patched = patched.replace(
+ /(CFBundleName<\/key>\s*.*?<\/string>)/,
+ `$1\n\tCFBundleDisplayName\n\t${APP_NAME}`
+ );
+ }
+
+ // Patch bundle identifier so macOS associates dock state correctly
+ if (currentBundleId && currentBundleId !== BUNDLE_ID) {
+ patched = replacePlistValue(patched, 'CFBundleIdentifier', BUNDLE_ID);
+ }
+
+ fs.writeFileSync(plistPath, patched, 'utf8');
+
+ // Verify
+ const verify = fs.readFileSync(plistPath, 'utf8');
+ const verifiedName = readPlistValue(verify, 'CFBundleName');
+ if (verifiedName !== APP_NAME) {
+ console.error('[patch-electron-mac] ❌ Patch failed — CFBundleName is still:', verifiedName);
+ return 1;
+ }
+
+ console.log('[patch-electron-mac] ✅ Patched Electron.app Info.plist');
+ console.log(` CFBundleName: "${currentName}" → "${APP_NAME}"`);
+ console.log(` CFBundleDisplayName: "${currentDisplayName || '(missing)'}" → "${APP_NAME}"`);
+ if (currentBundleId && currentBundleId !== BUNDLE_ID) {
+ console.log(` CFBundleIdentifier: "${currentBundleId}" → "${BUNDLE_ID}"`);
+ }
+ console.log(' The macOS dock will now show "StratoSort Core" in development.');
+
+ return 0;
+}
+
+if (require.main === module) {
+ process.exit(main());
+}
+
+module.exports = { main };
diff --git a/scripts/postinstall.js b/scripts/postinstall.js
index ef29573..0786e5d 100644
--- a/scripts/postinstall.js
+++ b/scripts/postinstall.js
@@ -53,6 +53,17 @@ function main(
}
}
+ // On macOS, patch the Electron.app Info.plist so the dock label and Activity
+ // Monitor show "StratoSort Core" instead of "Electron" during development.
+ if (platform === 'darwin') {
+ try {
+ const { main: patchMac } = require('./patch-electron-mac');
+ patchMac();
+ } catch (e) {
+ log.warn(`[postinstall] macOS Electron patch skipped: ${e.message}`);
+ }
+ }
+
log.log('\n[StratoSort] Setup complete!');
log.log(' Run: npm run dev');
log.log(' The app will use local AI engine (node-llama-cpp + Orama).\n');
diff --git a/src/main/simple-main.js b/src/main/simple-main.js
index fdd5543..311ed98 100644
--- a/src/main/simple-main.js
+++ b/src/main/simple-main.js
@@ -8,6 +8,14 @@ try {
const { app, BrowserWindow, ipcMain, dialog, shell, crashReporter } = require('electron');
+// Set the application name as early as possible.
+// On macOS this controls the dock label and application menu title.
+// Electron defaults to package.json "productName" → "name", but the raw Electron
+// binary in development reports itself as "Electron" to the OS. An explicit
+// assignment ensures the correct name in all contexts (dock, Activity Monitor menu
+// label, window title fallback) regardless of dev vs packaged mode.
+app.name = 'StratoSort Core';
+
const isDev = process.env.NODE_ENV === 'development';
// Logging utility
@@ -543,6 +551,28 @@ if (gotTheLock || process.env.STRATOSORT_FORCE_LAUNCH === '1') {
// Initialize services after app is ready
app.whenReady().then(async () => {
logger.info('[STARTUP] app.whenReady resolved');
+
+ // macOS: Set the dock icon to the StratoSort logo so it replaces the default
+ // Electron atom icon during development. In production (packaged) builds
+ // electron-builder bakes the icon into the .app bundle so this is a no-op.
+ if (process.platform === 'darwin' && app.dock) {
+ try {
+ const { nativeImage } = require('electron');
+ const iconPath = path.join(__dirname, '..', 'assets', 'icons', 'png', '512x512.png');
+ // Resolve from repo root in dev, or from resources in packaged build
+ const resolvedIcon = app.isPackaged
+ ? path.join(process.resourcesPath, 'assets', 'icons', 'png', '512x512.png')
+ : path.resolve(iconPath);
+ const fsSync = require('fs');
+ if (fsSync.existsSync(resolvedIcon)) {
+ app.dock.setIcon(nativeImage.createFromPath(resolvedIcon));
+ logger.info('[DOCK] Set macOS dock icon from', resolvedIcon);
+ }
+ } catch (dockErr) {
+ logger.debug('[DOCK] Could not set macOS dock icon:', dockErr?.message);
+ }
+ }
+
// FIX: Create a referenced interval to keep the event loop alive during startup
// This prevents premature exit when async operations use unreferenced timeouts
const startupKeepalive = trackInterval(() => {}, 1000);