Endpunkt-Status-Icon mit Ereignis-Log und Update-Erkennung ergaenzen

Ein neues Status-Icon im Titelbalken zeigt den Zustand der lokalen
Transkripte und der Serverabfrage; ein Klick oeffnet ein eigenes Fenster
mit persistiertem, zeitgestempeltem Ereignis-/Fehlerprotokoll (loeschbar).
Ausserdem erkennt die App beim Start Erstinstallation vs. Update
(plattformunabhaengig fuer Windows und Linux) und meldet Updates per
nativer Benachrichtigung.

Dazu: Versionierungs-Policy dokumentiert (package.json vor jedem Release
erhoehen, sonst greift weder die Update-Erkennung noch bleiben alte
Release-Dateien in release/ erhalten), sowie zwei neue Dokumente fuer
Endanwender (BENUTZERHANDBUCH.md) und eine Download-Webseite
(PRODUKTSEITE.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Winkler, Stefan
2026-08-04 11:07:07 +02:00
co-authored by Claude Opus 4.8
parent c1b899ec8b
commit e149a9118f
15 changed files with 894 additions and 32 deletions
+85 -2
View File
@@ -7,7 +7,7 @@
const fs = require('node:fs');
const path = require('node:path');
const { app, BrowserWindow, Tray, Menu, ipcMain, screen, nativeImage, shell } = require('electron');
const { app, BrowserWindow, Tray, Menu, ipcMain, screen, nativeImage, shell, Notification } = require('electron');
const { Store, loadConfig } = require('./store');
const { Collector } = require('./collector');
@@ -26,8 +26,12 @@ const ROOT = path.join(__dirname, '..', '..');
const SETTINGS_WIDTH = 520;
const SETTINGS_HEIGHT = 660;
const ERRORS_WIDTH = 420;
const ERRORS_HEIGHT = 480;
let win = null;
let settingsWin = null;
let errorsWin = null;
let tray = null;
let collector = null;
let store = null;
@@ -301,6 +305,50 @@ function openSettings() {
});
}
/** Fehler-/Ereignis-Log — ein Fenster, mehrfaches Öffnen holt es nur nach vorn. */
function openErrorLog() {
if (errorsWin && !errorsWin.isDestroyed()) {
errorsWin.show();
errorsWin.focus();
return;
}
const target = win && !win.isDestroyed()
? screen.getDisplayMatching(win.getBounds())
: screen.getPrimaryDisplay();
const area = target.workArea;
errorsWin = new BrowserWindow({
width: ERRORS_WIDTH,
height: ERRORS_HEIGHT,
x: Math.round(area.x + (area.width - ERRORS_WIDTH) / 2),
y: Math.round(area.y + Math.max(0, (area.height - ERRORS_HEIGHT) / 2)),
minWidth: 360,
minHeight: 320,
frame: false,
resizable: true,
skipTaskbar: false,
// Über dem Widget, das selbst always-on-top ist — sonst verschwindet der
// Dialog dahinter.
alwaysOnTop: true,
show: false,
backgroundColor: '#1e1e22',
icon: path.join(ROOT, 'assets', 'icon.png'),
webPreferences: {
preload: path.join(__dirname, '..', 'errors-preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
errorsWin.loadFile(path.join(__dirname, '..', 'errors', 'index.html'));
errorsWin.once('ready-to-show', () => errorsWin.show());
errorsWin.on('closed', () => {
errorsWin = null;
});
}
/**
* Übernimmt geänderte Einstellungen ohne Neustart und schreibt sie in die
* Datei. Unbekannte Felder und die `_`-Kommentare bleiben erhalten, weil die
@@ -345,6 +393,9 @@ function registerIpc() {
ipcMain.on('set-expanded', (_event, expanded) => applyExpanded(Boolean(expanded)));
ipcMain.on('hide-window', () => win && win.hide());
ipcMain.on('open-settings', openSettings);
ipcMain.on('open-error-log', openErrorLog);
ipcMain.on('errors:clear', () => collector && collector.clearErrors());
ipcMain.on('errors:close', () => errorsWin && errorsWin.close());
ipcMain.handle('settings:load', () => ({
config,
@@ -352,6 +403,7 @@ function registerIpc() {
configPath: CONFIG_FILE,
autostart: getAutostart(),
platform: process.platform,
version: app.getVersion(),
},
}));
@@ -386,6 +438,23 @@ async function main() {
store = new Store(STATE_FILE);
store.load();
// Neuinstallations-/Update-Erkennung: rein additiv, überschreibt oder
// löscht keine bestehenden Einstellungsdaten — nur `store.data.appVersion`
// wird gesetzt. Funktioniert identisch unter Windows (NSIS/portable) und
// Linux (AppImage/deb), da app.getVersion() plattformunabhängig aus den
// Paketmetadaten liest und state.json auf beiden Plattformen im selben
// Nutzerprofil-Ordner liegt (siehe paths.js).
const currentVersion = app.getVersion();
const previousVersion = store.data.appVersion;
let versionEvent = null;
if (previousVersion == null) {
versionEvent = { type: 'install', previousVersion: null, currentVersion };
} else if (previousVersion !== currentVersion) {
versionEvent = { type: 'update', previousVersion, currentVersion };
}
store.data.appVersion = currentVersion;
store.save();
registerIpc();
createWindow();
createTray();
@@ -393,9 +462,23 @@ async function main() {
if (config.opacity != null) win.setOpacity(config.opacity);
if (config.clickThrough) win.setIgnoreMouseEvents(true, { forward: true });
collector = new Collector({ config, store, configError: error });
collector = new Collector({ config, store, configError: error, versionEvent });
// Hinweis auf ein Update über die native Systembenachrichtigung — der
// Titelbalken ist mit dem Endpunkt-Status-Icon bereits eng, und eine
// Notification ist naturgemäß einmalig. Eine Neuinstallation bekommt
// bewusst keine Meldung: dafür gibt es nichts, worüber zu informieren wäre.
if (versionEvent?.type === 'update' && Notification.isSupported()) {
new Notification({
title: 'Claude Live Dashboard aktualisiert',
body: `Version ${versionEvent.previousVersion}${versionEvent.currentVersion}`,
icon: path.join(ROOT, 'assets', 'icon.png'),
}).show();
}
collector.on('update', (state) => {
if (win && !win.isDestroyed()) win.webContents.send('state', state);
if (errorsWin && !errorsWin.isDestroyed()) errorsWin.webContents.send('state', state);
if (tray) {
const parts = [`Claude · Woche ${Math.round(state.week.percent)} %`];
if (state.block.hasLimit) parts.push(`5h ${Math.round(state.block.percent)} %`);