Add a `desktop/` Electron app that embeds the existing Node server + node-pty (all-in-one): the window loads http://127.0.0.1:<port>/ and reuses the frontend unchanged; other LAN devices can still connect. The server needs zero changes — startServer(cfg)/loadConfig already support programmatic embedding. - Pure, unit-tested modules: port, shell, server-config, deep-link, notify-policy, notifications, live-poll, prefs, settings-store (94 tests; desktop/src ~97% cov) - Electron glue: main/window/tray/menu/preload/embedded-server/logger (hardened: contextIsolation, sandbox, deny foreign-origin navigation) - Native value: OS notifications driven by /live-sessions status, tray, deep links - Packaging (electron-builder -> arm64 .dmg): ships dist/ + public/ + node_modules on-disk under Resources so the server resolves its deps from /Applications; node-pty rebuilt for the Electron ABI - Docs: docs/DESKTOP_PLAN.md; PROGRESS_LOG updated Verified: desktop tsc clean; 1401 tests pass; coverage >=80% (desktop/src 97/95/100/97); .dmg built and launch-tested on arm64 (server boots, UI serves 200, node-pty loads).
54 lines
1.5 KiB
TypeScript
54 lines
1.5 KiB
TypeScript
/**
|
|
* desktop/src/menu.ts — the native application menu.
|
|
*
|
|
* Built entirely from Electron's built-in roles (copy/paste/reload/zoom/…) so it
|
|
* needs no cooperation from the (unchanged) frontend. There are deliberately no
|
|
* page-driving custom actions here — the desktop shell adds native chrome, not
|
|
* new terminal behaviour. The caller passes the result to Menu.setApplicationMenu.
|
|
*/
|
|
import { Menu, type MenuItemConstructorOptions } from 'electron'
|
|
|
|
export function buildAppMenu(): Menu {
|
|
const isMac = process.platform === 'darwin'
|
|
|
|
const template: MenuItemConstructorOptions[] = [
|
|
...(isMac ? [{ role: 'appMenu' } as MenuItemConstructorOptions] : []),
|
|
{
|
|
label: 'File',
|
|
submenu: [isMac ? { role: 'close' } : { role: 'quit' }],
|
|
},
|
|
{
|
|
label: 'Edit',
|
|
submenu: [
|
|
{ role: 'undo' },
|
|
{ role: 'redo' },
|
|
{ type: 'separator' },
|
|
{ role: 'cut' },
|
|
{ role: 'copy' },
|
|
{ role: 'paste' },
|
|
{ role: 'selectAll' },
|
|
],
|
|
},
|
|
{
|
|
label: 'View',
|
|
submenu: [
|
|
{ role: 'reload' },
|
|
{ role: 'forceReload' },
|
|
{ role: 'toggleDevTools' },
|
|
{ type: 'separator' },
|
|
{ role: 'resetZoom' },
|
|
{ role: 'zoomIn' },
|
|
{ role: 'zoomOut' },
|
|
{ type: 'separator' },
|
|
{ role: 'togglefullscreen' },
|
|
],
|
|
},
|
|
{
|
|
label: 'Window',
|
|
submenu: [{ role: 'minimize' }, { role: 'close' }],
|
|
},
|
|
]
|
|
|
|
return Menu.buildFromTemplate(template)
|
|
}
|