From 3fbb32e6705c30e2c81d55d5ba2275902bc356a3 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 13 May 2026 11:03:12 +0530 Subject: [PATCH 1/5] feat: linux app image dekstopName for icons in taskbar --- src-electron/package-lock.json | 12 ++++++------ src-electron/package.json | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src-electron/package-lock.json b/src-electron/package-lock.json index 19b2ec2c..776f661f 100644 --- a/src-electron/package-lock.json +++ b/src-electron/package-lock.json @@ -1,17 +1,17 @@ { "name": "phoenix-code-electron-shell", - "version": "5.1.5", + "version": "5.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "phoenix-code-electron-shell", - "version": "5.1.5", + "version": "5.1.8", "dependencies": { "keytar": "^7.9.0" }, "devDependencies": { - "electron": "^41.1.1", + "electron": "^41.5.1", "electron-builder": "^24.13.3" } }, @@ -1787,9 +1787,9 @@ } }, "node_modules/electron": { - "version": "41.1.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-41.1.1.tgz", - "integrity": "sha512-8bgvDhBjli+3Z2YCKgzzoBPh6391pr7Xv2h/tTJG4ETgvPvUxZomObbZLs31mUzYb6VrlcDDd9cyWyNKtPm3tA==", + "version": "41.5.2", + "resolved": "https://registry.npmjs.org/electron/-/electron-41.5.2.tgz", + "integrity": "sha512-bxlZeH/1WzvKbijmT7dH5MCqlJ/h2SfDJczNhHJe5srDJzZboiOQpwTlUFBecqDbGoBpmEekhDfjbubPnM8G7g==", "dev": true, "hasInstallScript": true, "license": "MIT", diff --git a/src-electron/package.json b/src-electron/package.json index bc299305..f54a70e7 100644 --- a/src-electron/package.json +++ b/src-electron/package.json @@ -8,6 +8,7 @@ ], "version": "5.1.8", "productName": "Phoenix Code Experimental Build", + "desktopName": "phoenix-code", "description": "Phoenix Code Experimental Build", "main": "main.js", "scripts": { @@ -19,7 +20,7 @@ "keytar": "^7.9.0" }, "devDependencies": { - "electron": "^41.1.1", + "electron": "^41.5.1", "electron-builder": "^24.13.3" } } \ No newline at end of file From e54ca1527d4f46782b2200f10ea68e9acc99ced6 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 13 May 2026 11:15:48 +0530 Subject: [PATCH 2/5] feat: vary electron AppImage desktopName/productName per stage Adds patchElectronStageBranding helper invoked from all 4 electron build entry points after config-effective.json is written. Writes the kebab-case stage identifier into both src-electron/electron-builder.yml productName (becomes the .desktop StartupWMClass) and src-electron/package.json desktopName (Electron 41.5+ Wayland app_id) so they match per stage: dev -> phoenix-code-experimental-build stage -> phoenix-code-pre-release prod -> phoenix-code Without per-stage identifiers the AppImages collapse into one taskbar entry / icon slot when installed side-by-side. Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 4 ++-- src-build/ci-setupDistFolders.js | 4 +++- src-build/createDistRelease.js | 4 +++- src-build/createDistTestRelease.js | 4 +++- src-build/createSrcRelease.js | 4 +++- src-build/utils.js | 29 +++++++++++++++++++++++++++++ 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index a1412225..0a260352 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "phoenix-code-ide", - "version": "5.1.5", + "version": "5.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "phoenix-code-ide", - "version": "5.1.5", + "version": "5.1.8", "hasInstallScript": true, "devDependencies": { "@tauri-apps/cli": "1.6.3", diff --git a/src-build/ci-setupDistFolders.js b/src-build/ci-setupDistFolders.js index e9b5b543..f4bddf35 100644 --- a/src-build/ci-setupDistFolders.js +++ b/src-build/ci-setupDistFolders.js @@ -9,7 +9,7 @@ import { import { EOL } from "os"; import os from "os"; import fs from 'fs'; -import {patchTauriConfigWithMetricsHTML} from "./utils.js"; +import {patchTauriConfigWithMetricsHTML, patchElectronStageBranding} from "./utils.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -182,6 +182,8 @@ function createElectronReleaseAssets() { console.log('gaMetricsURL:', effectiveConfig.gaMetricsURL); console.log('version:', effectiveConfig.version); fs.writeFileSync(configEffectivePath, JSON.stringify(effectiveConfig, null, 2)); + + patchElectronStageBranding(electronDir, effectiveConfig.productName); } ciCreateTauriDistReleaseConfig(); diff --git a/src-build/createDistRelease.js b/src-build/createDistRelease.js index a78db94c..5d68b896 100644 --- a/src-build/createDistRelease.js +++ b/src-build/createDistRelease.js @@ -4,7 +4,7 @@ import { dirname, join } from 'path'; import fs from 'fs'; import * as os from 'os'; import chalk from 'chalk'; -import { getPlatformDetails, patchTauriConfigWithMetricsHTML } from './utils.js'; +import { getPlatformDetails, patchTauriConfigWithMetricsHTML, patchElectronStageBranding } from './utils.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -164,6 +164,8 @@ function createElectronConfig() { console.log('gaMetricsURL:', effectiveConfig.gaMetricsURL); console.log('version:', effectiveConfig.version); fs.writeFileSync(configEffectivePath, JSON.stringify(effectiveConfig, null, 2)); + + patchElectronStageBranding(electronDir, effectiveConfig.productName); } function buildElectron() { diff --git a/src-build/createDistTestRelease.js b/src-build/createDistTestRelease.js index da15019b..2977ab4f 100644 --- a/src-build/createDistTestRelease.js +++ b/src-build/createDistTestRelease.js @@ -4,7 +4,7 @@ import { dirname, join } from 'path'; import fs from 'fs'; import * as os from 'os'; import chalk from 'chalk'; -import { getPlatformDetails } from './utils.js'; +import { getPlatformDetails, patchElectronStageBranding } from './utils.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -160,6 +160,8 @@ function createElectronConfig() { console.log('gaMetricsURL:', effectiveConfig.gaMetricsURL); console.log('version:', effectiveConfig.version); fs.writeFileSync(configEffectivePath, JSON.stringify(effectiveConfig, null, 2)); + + patchElectronStageBranding(electronDir, effectiveConfig.productName); } function buildElectron() { diff --git a/src-build/createSrcRelease.js b/src-build/createSrcRelease.js index 0d978fea..a8dfa8b3 100644 --- a/src-build/createSrcRelease.js +++ b/src-build/createSrcRelease.js @@ -4,7 +4,7 @@ import { dirname, join } from 'path'; import fs from 'fs'; import * as os from 'os'; import chalk from 'chalk'; -import { getPlatformDetails } from './utils.js'; +import { getPlatformDetails, patchElectronStageBranding } from './utils.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -136,6 +136,8 @@ function createElectronConfig() { console.log('gaMetricsURL:', effectiveConfig.gaMetricsURL); console.log('version:', effectiveConfig.version); fs.writeFileSync(configEffectivePath, JSON.stringify(effectiveConfig, null, 2)); + + patchElectronStageBranding(electronDir, effectiveConfig.productName); } function buildElectron() { diff --git a/src-build/utils.js b/src-build/utils.js index f13c4b3d..a36081dd 100644 --- a/src-build/utils.js +++ b/src-build/utils.js @@ -103,6 +103,35 @@ const METRIC_URL_FOR_STAGE = { "production": "https://phcode.dev/desktop-metrics.html" }; +/** + * Patches the electron build inputs so each stage has a unique app identity: + * - src-electron/package.json `desktopName` (Electron 41.5+ uses this as the Wayland app_id) + * - src-electron/electron-builder.yml `productName` (becomes the .desktop StartupWMClass + binary name) + * Both are set to the kebab-case form of `productName` so they match. Without matching values, + * the running window can't be paired to its .desktop entry and falls back to the generic Electron icon. + * + * @param {string} electronDir - absolute path to src-electron/ + * @param {string} productName - stage-specific display name from config-effective.json + * (e.g. "Phoenix Code", "Phoenix Code Pre-release", "Phoenix Code Experimental Build") + */ +export function patchElectronStageBranding(electronDir, productName) { + const appId = productName.toLowerCase().trim().replace(/\s+/g, '-'); + console.log(`Patching electron stage branding -> ${appId}`); + + const pkgPath = path.join(electronDir, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath)); + pkg.desktopName = appId; + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); + + const builderPath = path.join(electronDir, 'electron-builder.yml'); + const builderYml = fs.readFileSync(builderPath, 'utf8'); + const patched = builderYml.replace(/^productName:.*$/m, `productName: ${appId}`); + if (patched === builderYml) { + throw new Error(`Could not find productName line in ${builderPath}`); + } + fs.writeFileSync(builderPath, patched); +} + export function patchTauriConfigWithMetricsHTML(tauriConf, useClonedPhoenix) { const platform = getPlatformDetails().platform; let phoenixConfigPath = (platform === "win") ? `${__dirname}\\..\\..\\phoenix\\dist\\config.json` From 3aacf659561cc096a09bfd022ce6f6925ff65cd3 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 13 May 2026 11:22:49 +0530 Subject: [PATCH 3/5] fix: make patchElectronStageBranding idempotent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous "did we change anything" check inferred regex match failure from `patched === builderYml`, which also fires when the productName line is already the target value — so a second run (or any prod build, since the committed default is already `phoenix-code`) threw "Could not find productName line". Test the regex directly instead. Co-Authored-By: Claude Opus 4.7 (1M context) --- src-build/utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src-build/utils.js b/src-build/utils.js index a36081dd..0480541f 100644 --- a/src-build/utils.js +++ b/src-build/utils.js @@ -125,11 +125,11 @@ export function patchElectronStageBranding(electronDir, productName) { const builderPath = path.join(electronDir, 'electron-builder.yml'); const builderYml = fs.readFileSync(builderPath, 'utf8'); - const patched = builderYml.replace(/^productName:.*$/m, `productName: ${appId}`); - if (patched === builderYml) { + const productNameRegex = /^productName:.*$/m; + if (!productNameRegex.test(builderYml)) { throw new Error(`Could not find productName line in ${builderPath}`); } - fs.writeFileSync(builderPath, patched); + fs.writeFileSync(builderPath, builderYml.replace(productNameRegex, `productName: ${appId}`)); } export function patchTauriConfigWithMetricsHTML(tauriConf, useClonedPhoenix) { From c95ff5b965f741fadde356ee1da3495b41c5240f Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 13 May 2026 12:02:02 +0530 Subject: [PATCH 4/5] chore: set electron AppImage dev kebab as committed default src-electron/package.json desktopName and src-electron/electron-builder.yml productName were committed as the prod kebab (`phoenix-code`), but the matching display productName ("Phoenix Code Experimental Build") and config.json `stage: dev` are dev defaults. Align both to the dev kebab so a raw `npm run build:appimage` (no wrapper script) produces a coherent dev AppImage; CI wrappers continue to overwrite per stage via patchElectronStageBranding. Co-Authored-By: Claude Opus 4.7 (1M context) --- src-electron/electron-builder.yml | 2 +- src-electron/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src-electron/electron-builder.yml b/src-electron/electron-builder.yml index 8ba1d295..045d9701 100644 --- a/src-electron/electron-builder.yml +++ b/src-electron/electron-builder.yml @@ -1,5 +1,5 @@ appId: io.phcode.dev -productName: phoenix-code +productName: phoenix-code-experimental-build copyright: Copyright © 2024 phcode.dev asar: true diff --git a/src-electron/package.json b/src-electron/package.json index f54a70e7..d19588c4 100644 --- a/src-electron/package.json +++ b/src-electron/package.json @@ -8,7 +8,7 @@ ], "version": "5.1.8", "productName": "Phoenix Code Experimental Build", - "desktopName": "phoenix-code", + "desktopName": "phoenix-code-experimental-build", "description": "Phoenix Code Experimental Build", "main": "main.js", "scripts": { From 49fea2d6ce86127fc5c3b73e073df174d89fb3b4 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 13 May 2026 12:41:54 +0530 Subject: [PATCH 5/5] fix: load BrowserWindow icon from resources/ in packaged AppImage The BrowserWindow icon path resolved correctly in dev (`__dirname` = src-electron/, so `../src-tauri/icons/icon.png` reached the source tree) but not in the packaged AppImage, where `__dirname` is inside the asar and src-tauri/ is not bundled. Electron silently dropped the icon and the taskbar fell back to the generic Electron icon. Copy icon.png into the AppImage's resources/ via extraResources, and load it from process.resourcesPath when app.isPackaged. Co-Authored-By: Claude Opus 4.7 (1M context) --- src-electron/electron-builder.yml | 2 ++ src-electron/main.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src-electron/electron-builder.yml b/src-electron/electron-builder.yml index 045d9701..8a621dff 100644 --- a/src-electron/electron-builder.yml +++ b/src-electron/electron-builder.yml @@ -27,6 +27,8 @@ extraResources: to: "bin" - from: "phoenix-dist" to: "phoenix-dist" + - from: "../src-tauri/icons/icon.png" + to: "icon.png" extraMetadata: main: main.js diff --git a/src-electron/main.js b/src-electron/main.js index b548c491..29a2da4f 100644 --- a/src-electron/main.js +++ b/src-electron/main.js @@ -118,7 +118,9 @@ async function createWindow() { contextIsolation: true, nodeIntegration: false }, - icon: path.join(__dirname, '..', 'src-tauri', 'icons', 'icon.png') + icon: app.isPackaged + ? path.join(process.resourcesPath, 'icon.png') + : path.join(__dirname, '..', 'src-tauri', 'icons', 'icon.png') }); // Track window state for persistence