feedBack-desktop/package.json
topkoa 39251f3d12 feat(panes): pane pop-out windows + the system tray
feedBack core gained a pane system (window.feedBack.panes): live UI — a
mixer, a camera rig, a readout — authored once and hostable anywhere. In
a browser it pops out via window.open(). This gives it the desktop
treatment: a real BrowserWindow that remembers where you put it, can
float above everything, and lives in the system tray.

First Tray in the app. It exists because a popped-out pane is furniture:
you want it out of the way while you play and back instantly when you
don't — not hunted for behind the main window, and not cluttering the
taskbar. Minimizing a pane sends it to the tray; the tray menu lists
every pane with a checkmark and toggles it.

## The renderer owns the truth

Main never looks inside a pane. It owns OS surfaces only — windows and
their geometry — and learns what panes exist from a `pane:sync` push. The
tray menu is a VIEW of the renderer's registry, not a second copy of it.
When the tray toggles a pane it has no window for, it asks the renderer,
because only the renderer knows what opening one means (it might belong
in the dock).

## The pane window loads OUR origin, and that is load-bearing

A pane is fed over BroadcastChannel, which only reaches windows in the
same Chromium instance and origin. Push the URL anywhere else and the
pane opens looking perfect and never updates again. So `pane:open`
validates the URL against the same origin predicate the navigation guards
use (makeRendererOriginPredicate) and refuses anything else outright —
which also means we can never open arbitrary web content with the full
preload bridge attached. It is the same reason main.ts's
setWindowOpenHandler answers same-origin URLs with `allow` rather than
`deny` + openExternal.

## Details that bite

- sanitizeWindowBounds hard-floored at the MAIN window's 800x600. A 380x560
  pane restored through it would be silently inflated threefold. It now takes
  a WindowSizing; the main window passes its old values as the default, so
  every existing call site and the existing test are byte-for-byte unchanged.
- Pane geometry lives in the DESKTOP config, not the renderer's localStorage
  — localStorage is shared with the pane windows themselves (same origin), so
  a second writer there would race. setDesktopConfig merges shallowly, so
  paneWindows is read-modify-written or one pane's save would drop the rest.
- Pane windows are destroyed when the main window closes. Without the
  renderer there is nothing on the other end of their channel, so they would
  sit showing a frozen playhead forever — and a pane HIDDEN in the tray is
  still an open window, which would stop `window-all-closed` from ever firing
  and leave the app running as an invisible process.
- Geometry is persisted on move/resize, not only on close: a pane window can
  outlive the app in a crash, and the entire point is that you never place it
  twice.
- Electron's 'minimize' is not cancellable here (the listener takes no event),
  so a pane hides right after minimizing rather than preventing it. The window
  is skipTaskbar, so there is no animation to see.
- The tray icon is copied to dist/main/ by build:ts, the same trick
  splash.html and spinner.json use — so __dirname resolves it identically in
  dev and inside a packaged asar, with no app.isPackaged branch and nothing
  added to electron-builder's extraResources. An unreadable icon logs and
  skips the tray rather than creating an invisible one whose menu no one can
  ever reach.

Needs the matching core change (got-feedback/feedback#928), which registers
the `desktop` host when this bridge is present and falls back to a browser
pop-up when it isn't. An older core simply never calls these channels.

Signed-off-by: topkoa <topkoa@gmail.com>
2026-07-12 17:22:44 -04:00

158 lines
4.9 KiB
JSON

{
"name": "slopsmith-desktop",
"version": "0.3.0",
"description": "fee[dB]ack desktop app with integrated audio engine, VST hosting, and amp modeling",
"author": {
"name": "Byron",
"email": "byron@ougsoft.com"
},
"license": "AGPL-3.0-only",
"main": "dist/main/main.js",
"scripts": {
"typecheck": "tsc --noEmit",
"test": "node --test \"tests/*.test.js\"",
"build:ts": "tsc && node -e \"const fs=require('fs'),path=require('path');fs.copyFileSync('src/main/splash.html','dist/main/splash.html');fs.copyFileSync('src/main/spinner.json','dist/main/spinner.json');fs.copyFileSync('node_modules/lottie-web/build/player/lottie.min.js','dist/main/lottie.min.js');const dst='dist/main/images';if(fs.existsSync(dst))fs.rmSync(dst,{recursive:true});fs.mkdirSync(dst,{recursive:true});fs.readdirSync('src/main/images',{withFileTypes:true}).filter(d=>d.isFile()&&d.name.endsWith('.webp')).forEach(d=>fs.copyFileSync(path.join('src/main/images',d.name),path.join(dst,d.name)));fs.copyFileSync('resources/icons/icon.ico','dist/main/tray.ico');fs.copyFileSync('resources/icons/32x32.png','dist/main/tray.png')\"",
"build:audio": "bash scripts/build-audio.sh Release",
"build:audio:debug": "bash scripts/build-audio.sh Debug",
"rebuild:audio": "rm -rf build && bash scripts/build-audio.sh Release",
"build:native": "npm run build:audio",
"bundle:slopsmith": "bash scripts/bundle-slopsmith.sh",
"bundle:python": "bash scripts/bundle-python.sh",
"bundle:binaries": "bash scripts/bundle-binaries.sh",
"bundle:soundfont": "bash scripts/bundle-soundfont.sh",
"bundle": "bash scripts/bundle.sh",
"start": "npm run build:ts && electron .",
"dev": "npm run build:ts && electron .",
"dist": "npm run build:native && npm run bundle && npm run build:ts && electron-builder",
"dist:linux": "npm run dist -- --linux",
"dist:mac": "npm run dist -- --mac",
"dist:win": "npm run dist -- --win",
"postinstall": "electron-builder install-app-deps"
},
"build": {
"appId": "com.byron.slopsmith-desktop",
"productName": "fee[dB]ack",
"executableName": "feedback",
"extraMetadata": {
"name": "feedback-desktop"
},
"artifactName": "feedback-${version}-${arch}.${ext}",
"directories": {
"output": "release"
},
"files": [
"dist/**/*",
"build/Release/*.node",
"build/Release/libonnxruntime*",
"build/Release/onnxruntime*.dll"
],
"extraResources": [
{
"from": "LICENSE",
"to": "LICENSE"
},
{
"from": "THIRD_PARTY_LICENSES.md",
"to": "THIRD_PARTY_LICENSES.md"
},
{
"from": "README.md",
"to": "README.md"
},
{
"from": "resources/slopsmith",
"to": "slopsmith"
},
{
"from": "resources/python",
"to": "python"
},
{
"from": "resources/bin",
"to": "bin"
},
{
"from": "resources/default-irs",
"to": "default-irs"
},
{
"from": "resources/soundfonts",
"to": "soundfonts"
},
{
"from": "resources/models",
"to": "models"
}
],
"asarUnpack": [
"build/Release/*.node",
"build/Release/libonnxruntime*",
"build/Release/onnxruntime*.dll"
],
"linux": {
"target": [
"AppImage",
"deb"
],
"category": "Audio",
"icon": "resources/icons",
"files": [
"build/Release/slopsmith-vst-host"
],
"asarUnpack": [
"build/Release/slopsmith-vst-host"
]
},
"mac": {
"target": [
"dir"
],
"category": "public.app-category.music",
"icon": "resources/icons/icon.icns",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "resources/entitlements.mac.entitlements",
"entitlementsInherit": "resources/entitlements.mac.entitlements",
"notarize": true,
"extendInfo": {
"NSMicrophoneUsageDescription": "fee[dB]ack needs microphone access for guitar input and note detection."
},
"files": [
"build/Release/slopsmith-vst-scan",
"build/Release/slopsmith-vst-host"
],
"asarUnpack": [
"build/Release/slopsmith-vst-scan",
"build/Release/slopsmith-vst-host"
]
},
"win": {
"target": [
"dir"
],
"icon": "resources/icons/icon.ico",
"files": [
"build/Release/slopsmith-vst-host.exe"
],
"asarUnpack": [
"build/Release/slopsmith-vst-host.exe"
]
}
},
"dependencies": {
"lottie-web": "^5.13.0",
"node-addon-api": "^8.3.1",
"velopack": "0.0.1589-ga2c5a97"
},
"devDependencies": {
"@types/node": "^22.0.0",
"cmake-js": "^7.3.0",
"electron": "^35.0.0",
"electron-builder": "^25.1.8",
"typescript": "^5.7.0"
},
"cmake-js": {
"runtime": "electron"
}
}