Commit Graph

6 Commits

Author SHA1 Message Date
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
OmikronApex
83b8b66fe7 fix(ci): rename entitlements file — vpk requires .entitlements extension
Nightly run 29093119570 failed:
  --signEntitlements does not have an .entitlements extension
vpk validates the file extension. Rename
resources/entitlements.mac.plist -> entitlements.mac.entitlements
and update all references (electron-builder config, sign-macos-binaries.sh,
both workflows). Content unchanged; codesign/electron-builder accept any
extension.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:04:31 +02:00
Byron Gamatos
896e0f0ce4
chore(release): bump version to 0.3.0 for the 0.3.0-alpha series (#66)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 10:50:26 +02:00
Byron Gamatos
5188aab938
feat(config): real config reset/repair + migration framework (drop manual-delete) (#38)
Eliminates the fragile "delete the config folder before upgrading" tester
instruction, which was wrong-by-OS because the userData folder name was
derived inconsistently per platform (fee[dB]ack on macOS, slopsmith-desktop
on Linux/Windows).

A. Deterministic paths + migration framework
- Pin the userData name on every OS via app.setName('feedback-desktop') +
  build.extraMetadata.name; brand (productName 'fee[dB]ack') unchanged.
- One-time userData migration copies a legacy folder into the new one so
  upgraded users don't start fresh (atomic copy-then-rename, fail-soft).
  Runs before the single-instance lock / crashReporter, which would otherwise
  create userData and defeat the "new dir doesn't exist" gate.
- config-migrations.ts: versioned, ordered, idempotent, fail-soft migration
  runner stamped in CONFIG_DIR/config_version.json; logs the active CONFIG_DIR
  at startup (closes the Linux ~/.local/share/slopsmith shared-config gap).

B. In-app "Reset / repair configuration" (Settings panel)
- Granular options: reset app settings & caches, clear plugin state & cached
  Python deps, and full reset with default-OFF opt-ins for installed plugins /
  song library / ML caches.
- config-paths.ts is the single source of truth for per-OS path enumeration;
  the song library, installed plugins and ML caches are structurally confined
  to optInExtras and never wiped by the safe/full categories.
- Reset stops the backend, deletes immediate paths, includes SQLite WAL/SHM
  sidecars + the migration stamp on full reset, and defers Chromium/Crashpad
  state to next launch (consumed before any window reopens it). ML caches honor
  TORCH_HOME/HF_HOME. Empty selection is a no-op (backend left running).
- SECURITY: destructive resets require a native main-process confirmation
  dialog — the renderer bridge is reachable by plugin scripts, so a
  renderer-only confirm is not a sufficient gate.

Tests: node:test suites for path enumeration (per-OS + library/plugins
preserved), migration idempotency/fail-soft, reset delete pipeline guarantees,
userData migration, and deferred-deletion schedule/consume. `npm test` green
(adds a test script). codex review --base origin/main clean.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 22:13:24 +02:00
Byron Gamatos
facac93659
feat(brand): finish desktop chrome rebrand → fee[dB]ack (#23)
main/main split #21 already rebranded splash.html + the splash status
message. This finishes the remaining user-visible chrome:

- Window titles (both BrowserWindows): Slopsmith → fee[dB]ack
- Error dialogs: "failed to start" / "Please restart" (×4) + the
  Velopack updater-error dialog
- macOS mic-permission warning string
- crashReporter productName/companyName label
- package.json: productName → "fee[dB]ack" (+ safe executableName
  "feedback" and artifactName slug so the AppImage/deb filename and
  inner binary avoid the "[dB]" glob metacharacters), description,
  and NSMicrophoneUsageDescription

Deliberately unchanged for continuity:
- package.json "name" (config dir stays ~/.config/slopsmith-desktop —
  testers keep settings/cache) and "appId" (Velopack update identity)
- code comments + the [main] dev console.log

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:32:57 +02:00
Byron Gamatos
bd603184d5 Clean release snapshot 2026-06-16 18:48:12 +02:00