Tray icon with live transfer progress and tabs menu

- AppTray: template menu-bar icon (22pt, adapts to light/dark). While
  transfers run it swaps to pre-rendered frames — faded arrows + a fill bar
  showing aggregate doneBytes/totalBytes of the queue (nearest 5%, throttled
  500ms); back to the glyph when idle. No runtime image encoding.
- Menu: one entry per tab (checkmark on the active one) with live per-tab
  transfer count and percentage (jobs mapped via session ids), an overall
  progress row while transferring, Show <app>, Quit. Selecting a tab
  activates the window and switches to it.
- First use of webview->bun messages: PanelsService pushes workspacesSync
  (tab names, sessions, active id) via a new ready-gated send proxy; verified
  end-to-end in the running app.
- engine.aggregateProgress(): totals + per-session breakdown over non-terminal
  jobs (unit-verified idle/mid-transfer/idle).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jokin
2026-07-11 21:41:51 +02:00
co-authored by Claude Fable 5
parent c218cc2104
commit 42e5e5bb9a
10 changed files with 228 additions and 6 deletions
+11 -2
View File
@@ -7,6 +7,7 @@ import { applyApplicationMenu } from "./menu";
import { createAppRPC } from "./rpc-handlers";
import { ConnectionStore } from "./store/connections";
import { SettingsStore } from "./store/settings";
import { AppTray } from "./tray";
import { TransferEngine } from "./transfers/engine";
const PROD_URL = "views://app/index.html";
@@ -37,13 +38,14 @@ const edits = new EditManager(registry, engine, settings);
const events = {
sessionClosed: (_info: { sessionId: string; reason: string }) => {},
connectionsChanged: () => {},
workspacesSync: (_sync: import("../shared/rpc").WorkspaceSyncInfo) => {},
};
const rpc = createAppRPC({ registry, engine, store, settings, edits, events });
const url = await getMainViewUrl();
const isMac = process.platform === "darwin";
new BrowserWindow({
const win = new BrowserWindow({
title: BRANDING.appName,
url,
frame: {
@@ -63,9 +65,16 @@ new BrowserWindow({
rpc,
});
engine.onUpdate = (snapshot) => rpc.send.transferUpdate(snapshot);
const tray = new AppTray(win, engine);
tray.onAction = (action) => rpc.send.menuAction({ action });
engine.onUpdate = (snapshot) => {
rpc.send.transferUpdate(snapshot);
tray.updateProgress();
};
events.sessionClosed = (info) => rpc.send.sessionClosed(info);
events.connectionsChanged = () => void applyApplicationMenu(store);
events.workspacesSync = (sync) => tray.updateTabs(sync);
void settings.load().then((s) => engine.setConcurrency(effectiveConcurrency(s)));
void applyApplicationMenu(store);