From 79a05f40c1327f8584b7b915393bbcb6e95e35ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hendrik=20L=C3=B6sch?= Date: Thu, 12 Mar 2026 15:31:35 +0100 Subject: [PATCH 1/7] fix: removed broken electron menus --- electron/main.js | 60 ++-------------------- src/App.vue | 50 +----------------- src/components/projects/ProjectSummary.vue | 21 -------- src/components/projects/TechRadar.vue | 33 ------------ src/components/workspace/Workspace.vue | 13 ----- 5 files changed, 7 insertions(+), 170 deletions(-) diff --git a/electron/main.js b/electron/main.js index a6251e7..88be349 100644 --- a/electron/main.js +++ b/electron/main.js @@ -73,17 +73,13 @@ function createMenu() { submenu: [ { label: 'New Workspace', accelerator: 'CmdOrCtrl+Shift+N', click: send('new-workspace') }, { label: 'Open Workspace...', accelerator: 'CmdOrCtrl+Shift+O', click: send('open-workspace') }, + { label: 'Duplicate Workspace...', click: send('duplicate-workspace') }, { type: 'separator' }, { label: 'Save Workspace', accelerator: 'CmdOrCtrl+S', click: send('save-workspace') }, - { label: 'Toggle Autosave', click: send('toggle-autosave') }, { label: 'Save Workspace As...', click: send('save-workspace-as') }, - { label: 'Duplicate Workspace...', click: send('duplicate-workspace') }, - { type: 'separator' }, + { label: 'Toggle Autosave', click: send('toggle-autosave') }, { label: 'Close Workspace', click: send('close-workspace') }, { type: 'separator' }, - { label: 'Save', accelerator: 'CmdOrCtrl+Alt+S', click: send('save') }, - { label: 'Save All', accelerator: 'CmdOrCtrl+Alt+Shift+S', click: send('save-all') }, - { type: 'separator' }, { label: 'Exit', accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Alt+F4', click: () => app.quit() } ] }, @@ -95,11 +91,6 @@ function createMenu() { submenu: [ { label: 'New Project', click: send('projects-new') }, { label: 'Import Project...', click: send('projects-import') }, - { type: 'separator' }, - { label: 'Duplicate Project', click: send('projects-duplicate') }, - { label: 'Export Project As...', click: send('projects-save-as') }, - { type: 'separator' }, - { label: 'Project Settings', click: send('projects-settings') } ] }, // ── 3. Questionnaires (enabled when at least one project exists) ────── @@ -110,30 +101,9 @@ function createMenu() { submenu: [ { label: 'New Questionnaire', click: send('questionnaires-new') }, { label: 'Import Questionnaire...', click: send('questionnaires-import') }, - { type: 'separator' }, - { label: 'Duplicate Questionnaire', click: send('questionnaires-duplicate') }, - { label: 'Export Questionnaire As...', click: send('questionnaires-save-as') }, - { type: 'separator' }, - { label: 'Delete Questionnaire', click: send('questionnaires-delete') }, - { type: 'separator' }, - { label: 'Questionnaire Settings', click: send('questionnaires-settings') } ] }, - // ── 4. Radar (enabled when a project is selected) ───────────────────── - { - id: 'menu-radar', - label: 'Radar', - enabled: false, - submenu: [ - { label: 'Open Tech Radar', click: send('radar-open') }, - { type: 'separator' }, - { label: 'Export as ThoughtWorks JSON', click: send('radar-export-json') }, - { label: 'Download as PNG', click: send('radar-export-png') }, - { type: 'separator' }, - { label: 'Radar Settings', click: send('radar-settings') } - ] - }, - // ── 5. View ─────────────────────────────────────────────────────────── + // ── 4. View ─────────────────────────────────────────────────────────── { label: 'View', submenu: [ @@ -149,7 +119,7 @@ function createMenu() { { label: 'Toggle Sidebar', click: send('view-toggle-sidebar') } ] }, - // ── 6. Help ─────────────────────────────────────────────────────────── + // ── 5. Help ─────────────────────────────────────────────────────────── { label: 'Help', submenu: [ @@ -164,32 +134,12 @@ function createMenu() { return applicationMenu; } -function updateMenuState({ hasWorkspace = false, hasProjects = false, hasActiveProject = false, hasActiveQuestionnaire = false } = {}) { +function updateMenuState({ hasWorkspace = false, hasProjects = false } = {}) { if (!applicationMenu) return; const projectsItem = applicationMenu.items.find((i) => i.label === 'Projects'); const questItem = applicationMenu.items.find((i) => i.label === 'Questionnaires'); - const radarItem = applicationMenu.items.find((i) => i.label === 'Radar'); if (projectsItem) projectsItem.enabled = hasWorkspace; if (questItem) questItem.enabled = hasProjects; - if (radarItem) radarItem.enabled = hasActiveProject; - - // Disable specific Projects submenu items if no project is selected - if (projectsItem?.submenu?.items) { - projectsItem.submenu.items.forEach((item) => { - if (['Duplicate Project', 'Export Project As...', 'Project Settings'].includes(item.label)) { - item.enabled = hasActiveProject; - } - }); - } - - // Disable specific Questionnaires submenu items if no questionnaire is selected - if (questItem?.submenu?.items) { - questItem.submenu.items.forEach((item) => { - if (['Duplicate Questionnaire', 'Export Questionnaire As...', 'Delete Questionnaire', 'Questionnaire Settings'].includes(item.label)) { - item.enabled = hasActiveQuestionnaire; - } - }); - } } function createWindow() { diff --git a/src/App.vue b/src/App.vue index 6874d02..d9433cc 100644 --- a/src/App.vue +++ b/src/App.vue @@ -191,9 +191,7 @@ export default { if (!window.electronAPI) return window.electronAPI.updateMenuState({ hasWorkspace: !workspaceDirNeeded.value, - hasProjects: workspace.value.projects.length > 0, - hasActiveProject: !!activeProjectId.value, - hasActiveQuestionnaire: !!store.activeQuestionnaireId + hasProjects: workspace.value.projects.length > 0 }) } @@ -250,18 +248,6 @@ export default { case 'projects-import': store.dispatchMenuAction('import-project') break - case 'projects-duplicate': - if (activeProjectId.value) store.duplicateProject(activeProjectId.value) - break - case 'projects-save-as': - if (activeProjectId.value) store.exportProject(activeProjectId.value) - break - case 'projects-settings': - if (activeProjectId.value) { - store.openProjectSummary(activeProjectId.value) - store.dispatchMenuAction('project-settings') - } - break // ── Questionnaires ──────────────────────────────────────────── case 'questionnaires-new': @@ -270,38 +256,6 @@ export default { case 'questionnaires-import': store.dispatchMenuAction('import-questionnaire', { projectId: activeProjectId.value }) break - case 'questionnaires-duplicate': - if (store.activeQuestionnaireId) store.duplicateQuestionnaire(store.activeQuestionnaireId) - break - case 'questionnaires-save-as': - if (store.activeQuestionnaireId) store.saveQuestionnaire(store.activeQuestionnaireId) - break - case 'questionnaires-delete': - if (store.activeQuestionnaireId) store.deleteQuestionnaire(store.activeQuestionnaireId) - break - case 'questionnaires-settings': - store.dispatchMenuAction('questionnaire-settings') - break - - // ── Radar ───────────────────────────────────────────────────── - case 'radar-open': - if (activeProjectId.value) { - store.openProjectSummary(activeProjectId.value) - store.dispatchMenuAction('radar-open') - } - break - case 'radar-export-json': - store.dispatchMenuAction('radar-export-json') - break - case 'radar-export-png': - store.dispatchMenuAction('radar-export-png') - break - case 'radar-settings': - if (activeProjectId.value) { - store.openProjectSummary(activeProjectId.value) - store.dispatchMenuAction('radar-settings') - } - break // ── View ────────────────────────────────────────────────────── case 'view-toggle-sidebar': @@ -320,7 +274,7 @@ export default { // Sync menu enabled states whenever relevant store state changes if (isElectron) { watch( - () => [workspace.value.projects.length, activeProjectId.value, workspaceDirNeeded.value, store.activeQuestionnaireId], + () => [workspace.value.projects.length, workspaceDirNeeded.value], () => syncMenuState(), { immediate: true, deep: false } ) diff --git a/src/components/projects/ProjectSummary.vue b/src/components/projects/ProjectSummary.vue index ae6b037..c87e113 100644 --- a/src/components/projects/ProjectSummary.vue +++ b/src/components/projects/ProjectSummary.vue @@ -186,27 +186,6 @@ export default { store.updateProjectVisibilitySettings(props.projectId, settings) } - // React to menu actions – only when this project is the active one - watch( - () => store.pendingMenuAction, - (pending) => { - if (!pending) return - if (store.activeProjectId !== props.projectId) return - const { action } = pending - if (action === 'project-settings') { - categorySettingsOpen.value = true - store.clearMenuAction() - } else if (action === 'radar-open') { - activeTab.value = 'radar' - store.clearMenuAction() - } else if (action === 'radar-settings') { - activeTab.value = 'radar' - store.clearMenuAction() - } - }, - { deep: true } - ) - return { project, questionnaires, diff --git a/src/components/projects/TechRadar.vue b/src/components/projects/TechRadar.vue index f673099..ac75cd3 100644 --- a/src/components/projects/TechRadar.vue +++ b/src/components/projects/TechRadar.vue @@ -792,21 +792,6 @@ export default { } }) - // Open quadrant config dialog when menu action 'radar-settings' is dispatched - watch( - () => store.pendingMenuAction, - (pending) => { - if (!pending) return - if (store.activeProjectId !== props.projectId) return - const { action } = pending - if (action === 'radar-settings') { - quadrantConfigDialog.value = true - store.clearMenuAction() - } - }, - { deep: true } - ) - // ── Visible ring tracking ──────────────────────────────────────────────── // List of visible ring indices (0-4 corresponding to RING_META) const visibleRingIndices = computed(() => { @@ -1673,24 +1658,6 @@ export default { } } - // React to radar export menu actions - watch( - () => store.pendingMenuAction, - (pending) => { - if (!pending) return - if (store.activeProjectId !== props.projectId) return - const { action } = pending - if (action === 'radar-export-json') { - exportRadarJson() - store.clearMenuAction() - } else if (action === 'radar-export-png') { - downloadRadar() - store.clearMenuAction() - } - }, - { deep: true } - ) - return { SIZE, CX, CY, OUTER_R, computedRings, BLIP_R, RING_META, tooltipWidth, diff --git a/src/components/workspace/Workspace.vue b/src/components/workspace/Workspace.vue index 8270382..2024b10 100644 --- a/src/components/workspace/Workspace.vue +++ b/src/components/workspace/Workspace.vue @@ -121,19 +121,6 @@ export default { store.updateQuestionnaireCategories(activeQuestionnaireId.value, newCategories) } - // React to questionnaire-settings menu action - watch( - () => store.pendingMenuAction, - (pending) => { - if (!pending) return - if (pending.action === 'questionnaire-settings' && activeQuestionnaireId.value) { - configOpen.value = true - store.clearMenuAction() - } - }, - { deep: true } - ) - return { workspaceTabs, activeTab, From c967ddcf51f57354f393742f986be198e7501b7e Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 13 Mar 2026 06:54:23 +0000 Subject: [PATCH 2/7] chore: pre-release v1.3.2-dev.5 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e63e0e..b7d7d8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "solution-inventory-pwa", - "version": "1.3.2-dev.4", + "version": "1.3.2-dev.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "solution-inventory-pwa", - "version": "1.3.2-dev.4", + "version": "1.3.2-dev.5", "hasInstallScript": true, "dependencies": { "html-to-image": "^1.11.13", diff --git a/package.json b/package.json index 0a3cb76..ce9d862 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solution-inventory-pwa", - "version": "1.3.2-dev.4", + "version": "1.3.2-dev.5", "description": "Solution Inventory - A questionnaire-based application for project assessment", "author": "Hendrik Lösch ", "private": true, From 1613d0631724962f89aacfbffa42f8633ae74cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hendrik=20L=C3=B6sch?= Date: Fri, 13 Mar 2026 08:21:36 +0100 Subject: [PATCH 3/7] chore: formated and documented code for electron app --- .editorconfig | 25 ++++++ .github/agents/electron.agent.md | 57 ++++++++++++ .github/agents/vue.agent.md | 2 +- cucumber-report.html | 2 +- docs/todos.md | 4 - electron/main.js | 148 ++++++++++++++++++++++++++----- electron/preload.js | 107 +++++++++++++++++++--- 7 files changed, 308 insertions(+), 37 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/agents/electron.agent.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d40071a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig helps maintain consistent coding styles +# https://editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.{js,vue,json}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false +max_line_length = off + +[*.{yml,yaml}] +indent_size = 2 + +[package.json] +indent_size = 2 \ No newline at end of file diff --git a/.github/agents/electron.agent.md b/.github/agents/electron.agent.md new file mode 100644 index 0000000..fb50294 --- /dev/null +++ b/.github/agents/electron.agent.md @@ -0,0 +1,57 @@ +--- +description: 'Expert Electron & Cross-Platform Web Engineer specializing in performance, security, IPC optimization, and isomorphic web architectures' +name: 'Expert Electron Engineer' +model: 'Claude Sonnet 4.5' +tools: ["changes", "codebase", "edit/editFiles", "extensions", "new", "problems", "runCommands", "runTasks", "search", "searchResults", "terminalLastCommand", "terminalSelection", "testFailure", "usages", "vscodeAPI"] +--- + +# Expert Electron & Cross-Platform Frontend Engineer + +You are a world-class Electron expert with deep knowledge of cross-platform desktop application architecture, Node.js, Chromium internals, and isomorphic web development. + +## Your Expertise + +- **Electron Core**: Main vs. Renderer process architecture, application lifecycle, native menus, dialogs, and window management +- **Isomorphic Architecture (Web & Desktop)**: Designing web apps that run flawlessly in a standard web browser while progressively enhancing their capabilities when running inside an Electron container +- **Inter-Process Communication (IPC)**: Efficient message passing, `ipcMain`, `ipcRenderer`, and handling complex asynchronous data flows across process boundaries +- **Security & Context Isolation**: Deep understanding of `contextBridge`, disabling `nodeIntegration`, Content Security Policies (CSP), and secure protocol handlers +- **Performance Optimization**: Main process startup time reduction (V8 snapshots, lazy loading), memory leak prevention, minimizing IPC overhead, and UI thread unblocking +- **Native Integrations**: File system access, OS-level notifications, system tray, auto-updaters, and hardware/device access (USB/Bluetooth) handling +- **Build & Distribution**: `electron-builder`, `electron-forge`, code signing (macOS/Windows), notarization, and multi-platform CI/CD pipelines +- **Testing**: End-to-end testing with Playwright/WebdriverIO for Electron, and mocking IPC for isolated renderer testing +- **Tooling**: Vite (`electron-vite`), Webpack, esbuild, TypeScript integration, and modern debugging techniques for multiple processes + +## Your Approach + +- **Web-First / Browser-Compatible**: Build the renderer process as a standard web application. Fallback to web APIs (e.g., REST/IndexedDB) gracefully if Electron-specific APIs (`window.myAppAPI`) are undefined. +- **Secure by Default**: Never enable `nodeIntegration` in the renderer. Always use `contextIsolation: true` and expose highly specific, sanitized APIs via preload scripts. +- **Performance-Aware**: Avoid synchronous IPC calls (`sendSync`) at all costs, as they block the renderer thread. Lazy-load heavy Node.js modules in the main process only when needed. +- **Thin Preload Layer**: Treat the preload script strictly as a bridge. Keep business logic out of it; it should only map IPC calls to a globally accessible `window` object. +- **Graceful Degradation**: Structure feature flags or environment variables so the UI adapts cleanly if OS-level features are unavailable (e.g., when running in a normal web browser). + +## Guidelines + +- Structure code into strict domains: `src/main` (Node.js), `src/preload` (Bridge), and `src/renderer` (UI/Web App). +- Ensure the `renderer` directory contains NO direct `require('electron')` calls. It must rely entirely on standard Web APIs or the custom `window` bridge. +- Validate and sanitize all inputs received via `ipcMain.handle` or `ipcMain.on` before processing them in Node.js. +- Use `ipcMain.handle` and `invoke` for request/response patterns instead of event-based `send`/`on` combinations, unless handling streams or continuous events. +- Implement robust error handling across the IPC bridge; ensure native errors are serialized properly before being sent to the renderer. +- Utilize standard web performance metrics (LCP, FID, CLS) for the renderer, alongside Electron-specific metrics (app ready time, memory consumption). +- Clean up event listeners in both the main process and renderer to prevent memory leaks when windows are closed or components are unmounted. + +## Common Scenarios You Excel At + +- Refactoring legacy Electron apps with `nodeIntegration: true` to use modern `contextBridge` without breaking functionality +- Optimizing bundle sizes and startup times for heavy Electron applications +- Designing a robust abstraction layer so a single codebase compiles to both a generic Web App and a native-feeling Electron Desktop App +- Debugging memory leaks originating from retained IPC handlers or hidden `BrowserWindow` instances +- Implementing and troubleshooting secure auto-update flows (`electron-updater`) +- Setting up Playwright tests that seamlessly launch and interact with the packaged Electron binary + +## Response Style + +- Provide clear separation of concerns in code examples (explicitly label `main.ts`, `preload.ts`, and `renderer.vue/ts`). +- Explain the security and performance implications of IPC design choices. +- Always assume the user wants the UI to remain fully functional in a standard browser context unless explicitly told otherwise. +- Call out OS-specific quirks (macOS vs. Windows vs. Linux) when implementing native features. +- Favor minimal, explicit API surfaces in the preload script over exposing generic "send everything" channels. \ No newline at end of file diff --git a/.github/agents/vue.agent.md b/.github/agents/vue.agent.md index fa7b69d..ff5561a 100644 --- a/.github/agents/vue.agent.md +++ b/.github/agents/vue.agent.md @@ -2,7 +2,7 @@ description: 'Expert Vue.js frontend engineer specializing in Vue 3 Composition API, reactivity, state management, Vuetify, testing, and performance' name: 'Expert Vue.js Frontend Engineer' model: 'Claude Sonnet 4.5' -tools: ["changes", "codebase", "edit/editFiles", "extensions", "new", "openSimpleBrowser", "problems", "runCommands", "runTasks", "search", "searchResults", "terminalLastCommand", "terminalSelection", "testFailure", "usages", "vscodeAPI"] +tools: ["changes", "codebase", "edit/editFiles", "extensions", "new", "problems", "runCommands", "runTasks", "search", "searchResults", "terminalLastCommand", "terminalSelection", "testFailure", "usages", "vscodeAPI"] --- # Expert Vue.js Frontend Engineer diff --git a/cucumber-report.html b/cucumber-report.html index 3ab1f0c..372cfcd 100644 --- a/cucumber-report.html +++ b/cucumber-report.html @@ -46,7 +46,7 @@