diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dd6e803
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+dist/
+*.log
+.DS_Store
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b1cfa44
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# electron-tests
+
+Electron tray/window test app for CI/CD validation.
+
+## Features
+
+- Tray icon + context menu
+- Main desktop window
+- Simple status UI showing runtime versions
+
+## Run locally
+
+```bash
+npm ci
+npm start
+```
+
+## Build AppImage
+
+```bash
+npm run build:linux:amd64
+npm run build:linux:arm64
+```
+
+Artifacts are produced via CI workflows:
+
+- `electron-linux-amd64`
+- `electron-linux-arm64`
diff --git a/index.html b/index.html
deleted file mode 100644
index 154b9de..0000000
--- a/index.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
This is a test desktop window for the Electron tray application.
-
Close window to hide to tray, use tray menu to show it again.
-
-
-
diff --git a/main.js b/main.js
deleted file mode 100644
index 625a499..0000000
--- a/main.js
+++ /dev/null
@@ -1,70 +0,0 @@
-const { app, BrowserWindow, Menu, Tray } = require('electron');
-const path = require('path');
-
-let tray = null;
-let mainWindow = null;
-
-function createWindow() {
- mainWindow = new BrowserWindow({
- width: 700,
- height: 420,
- title: 'Electron Tray Demo',
- webPreferences: {
- contextIsolation: true,
- nodeIntegration: false,
- },
- });
- mainWindow.loadFile(path.join(__dirname, 'index.html'));
- mainWindow.on('close', (event) => {
- if (!app.isQuiting) {
- event.preventDefault();
- mainWindow.hide();
- }
- });
-}
-
-function createTray() {
- const iconPath = path.join(__dirname, 'assets', 'trayTemplate.png');
- tray = new Tray(iconPath);
- tray.setToolTip('Electron Tray Demo');
-
- const contextMenu = Menu.buildFromTemplate([
- {
- label: 'Show Window',
- click: () => {
- if (mainWindow) {
- mainWindow.show();
- mainWindow.focus();
- }
- },
- },
- {
- label: 'Quit',
- click: () => {
- app.isQuiting = true;
- app.quit();
- },
- },
- ]);
-
- tray.setContextMenu(contextMenu);
- tray.on('double-click', () => {
- if (mainWindow) {
- mainWindow.show();
- mainWindow.focus();
- }
- });
-}
-
-app.whenReady().then(() => {
- createWindow();
- createTray();
-
- app.on('activate', () => {
- if (BrowserWindow.getAllWindows().length === 0) createWindow();
- });
-});
-
-app.on('window-all-closed', () => {
- // keep tray app alive on Linux/macOS
-});
diff --git a/package.json b/package.json
index 85ae48a..8cd6bb3 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "electron-tray-demo",
"version": "1.0.0",
"description": "Electron tray demo app",
- "main": "main.js",
+ "main": "src/main/index.js",
"author": "marksaitis",
"license": "MIT",
"scripts": {
@@ -18,8 +18,7 @@
"appId": "io.swissline.electrontraydemo",
"productName": "electron_tray_demo",
"files": [
- "main.js",
- "index.html",
+ "src/**/*",
"assets/**/*",
"package.json"
],
diff --git a/src/main/index.js b/src/main/index.js
new file mode 100644
index 0000000..f7e7a9d
--- /dev/null
+++ b/src/main/index.js
@@ -0,0 +1,86 @@
+const { app, BrowserWindow, Menu, Tray, nativeImage } = require('electron');
+const path = require('path');
+
+let tray = null;
+let mainWindow = null;
+
+function createMainWindow() {
+ mainWindow = new BrowserWindow({
+ width: 900,
+ height: 560,
+ minWidth: 720,
+ minHeight: 420,
+ title: 'Electron Test App',
+ backgroundColor: '#0f172a',
+ webPreferences: {
+ preload: path.join(__dirname, '..', 'preload', 'index.js'),
+ contextIsolation: true,
+ nodeIntegration: false,
+ sandbox: true,
+ },
+ });
+
+ mainWindow.loadFile(path.join(__dirname, '..', 'renderer', 'index.html'));
+
+ mainWindow.on('close', (event) => {
+ if (!app.isQuiting) {
+ event.preventDefault();
+ mainWindow.hide();
+ }
+ });
+}
+
+function buildTrayMenu() {
+ return Menu.buildFromTemplate([
+ {
+ label: 'Show window',
+ click: () => {
+ if (mainWindow) {
+ mainWindow.show();
+ mainWindow.focus();
+ }
+ },
+ },
+ {
+ type: 'separator',
+ },
+ {
+ label: 'Quit',
+ click: () => {
+ app.isQuiting = true;
+ app.quit();
+ },
+ },
+ ]);
+}
+
+function createTray() {
+ const iconPath = path.join(__dirname, '..', '..', 'assets', 'trayTemplate.png');
+ const icon = nativeImage.createFromPath(iconPath);
+ tray = new Tray(icon);
+ tray.setToolTip('Electron Test');
+ tray.setContextMenu(buildTrayMenu());
+ tray.on('double-click', () => {
+ if (mainWindow) {
+ mainWindow.show();
+ mainWindow.focus();
+ }
+ });
+}
+
+app.whenReady().then(() => {
+ createMainWindow();
+ createTray();
+
+ app.on('activate', () => {
+ if (BrowserWindow.getAllWindows().length === 0) {
+ createMainWindow();
+ } else if (mainWindow) {
+ mainWindow.show();
+ }
+ });
+});
+
+app.on('window-all-closed', () => {
+ // keep tray app alive by default
+});
diff --git a/src/preload/index.js b/src/preload/index.js
new file mode 100644
index 0000000..c1c3cbb
--- /dev/null
+++ b/src/preload/index.js
@@ -0,0 +1,6 @@
+const { contextBridge } = require('electron');
+
+contextBridge.exposeInMainWorld('electronTest', {
+ appName: 'Electron Test',
+ runtime: process.versions,
+});
diff --git a/src/renderer/index.html b/src/renderer/index.html
new file mode 100644
index 0000000..4ea3a15
--- /dev/null
+++ b/src/renderer/index.html
@@ -0,0 +1,28 @@
+
+
+
+
+ Electron Test App
+ Tray + Window demo used for CI/CD validation.
+
+
+ Status
+
+ - Window launched ✅
+ - Tray icon active ✅
+ - Platform: ...
+ - Electron: ...
+ - Node: ...
+ - Chrome: ...
+
+
+
+
+
+
diff --git a/src/renderer/renderer.js b/src/renderer/renderer.js
new file mode 100644
index 0000000..accf9a9
--- /dev/null
+++ b/src/renderer/renderer.js
@@ -0,0 +1,6 @@
+const versions = (window.electronTest && window.electronTest.runtime) || {};
+
+document.getElementById('platform').textContent = navigator.userAgentData?.platform || navigator.platform || 'unknown';
+document.getElementById('electron').textContent = versions.electron || 'unknown';
+document.getElementById('node').textContent = versions.node || 'unknown';
+document.getElementById('chrome').textContent = versions.chrome || 'unknown';
diff --git a/src/renderer/styles.css b/src/renderer/styles.css
new file mode 100644
index 0000000..dfd3203
--- /dev/null
+++ b/src/renderer/styles.css
@@ -0,0 +1,22 @@
+* { box-sizing: border-box; }
+body {
+ margin: 0;
+ font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
+ color: #e2e8f0;
+ background: radial-gradient(circle at top left, #1e293b, #0f172a 55%);
+}
+.page {
+ max-width: 980px;
+ margin: 0 auto;
+ padding: 24px;
+}
+h1 { margin: 0 0 8px; font-size: 32px; }
+.muted { opacity: .8; margin: 0 0 16px; }
+.card {
+ border: 1px solid #334155;
+ border-radius: 16px;
+ padding: 16px;
+ background: rgba(30, 41, 59, 0.5);
+}
+ul { margin: 0; padding-left: 20px; }
+li { margin: 8px 0; }