Commit Graph
22 Commits
Author SHA1 Message Date
2a4396b7b7 Linux AppImage self-update on the nightly channel (#119)
Ship CI / CI (push) Has been cancelled
Addon CI / addon (arm64, macos-14, mac) (push) Has been cancelled
Addon CI / addon (x64, ubuntu-22.04, linux) (push) Has been cancelled
Addon CI / addon (x64, windows-latest, win) (push) Has been cancelled
* feat(update): Linux AppImage self-update on the nightly channel

Adds a self-update engine for the Linux AppImage build. There's no
Velopack pipeline for Linux (Windows/macOS use it, Linux doesn't), so
this is a small, purpose-built GitHub-releases checker instead:

- On the nightly channel, compares the commit baked into the running
  build (dist/main/build-info.json, written at build time) against the
  published nightly's target_commitish. A mismatch means the running
  build is behind, so it's offered as an update — this sidesteps the
  fact that the AppImage's filename and app.getVersion() never change
  between nightly builds, so semver comparison can't detect a new one.
- The check returns immediately and the ~1.5GB download runs in the
  background with live progress (a new update:progress IPC event), so
  the UI never blocks or freezes waiting on it.
- The download streams straight to disk (no buffering the whole file in
  memory) and is swapped in with an atomic rename next to the running
  AppImage. The stale-generation check (a channel switch or new check
  invalidating an in-flight download) runs before that swap, and a
  failed or superseded download always cleans up its temp file.
- Applying the update spawns the (already-swapped-in) AppImage as a
  detached process and waits for a real 'spawn' confirmation before
  quitting this one, rather than assuming success — child_process.spawn
  can fail asynchronously, and quitting on an unconfirmed relaunch could
  leave the user with nothing running.
- The pure idle/staged/download decision is split into
  linux-update-decision.ts with a small truth-table test, and every
  main-process decision point (and the equivalent renderer-side
  actions, in the companion feedBack PR) is traced through a new
  update:diag IPC event that lands in the app's existing "Export
  Diagnostics" console-capture bundle — this is how the handful of real
  bugs below were actually root-caused, from real device captures
  rather than guesswork.

Also removes a forgotten, dead second implementation of the
update-channel UI (src/renderer/screen.js's
setupUpdateChannelControls() + its markup in settings.html), left over
from before this work discovered the real, visible System-tab update
UI lives in the feedBack repo. It was still wired up in the
audio_engine plugin's own settings panel and silently called
setChannel() with a stale channel value every time that panel
rendered — invisibly corrupting the real UI's state. This was the
actual root cause of several rounds of flaky, hard-to-reproduce
on-device behavior (a stuck "unsupported" warning, downloads starting
without an explicit check, etc.) chased down via the diagnostic
tracing above; once found, no other logic needed to change.

Dev tooling only, not used by CI: forces --platform linux/amd64 in the
local Docker build wrapper (the Linux target is x86_64-only end to
end — needed on Apple Silicon, where Rosetta chokes on a foreign-arch
binary inside an otherwise-native container) and adds a SLOPSMITH_REPO
override so a contributor without push access to the core repo can
bundle a fork branch for a local test build.

Verified end-to-end on a Steam Deck across many build/deploy rounds:
fresh launch, channel selection, check, background download with live
progress, atomic swap, and relaunch onto the new build — confirmed via
a real Export Diagnostics capture showing a clean, fully-accounted-for
trace with zero orphaned state transitions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(update): fail safe when nightly release isn't pinned to a commit SHA

GitHub sets a release's target_commitish to whatever it was published
against — a 40-char SHA only if pinned, otherwise a branch name like
"main". The Linux update decision compares it SHA-vs-SHA, so a branch
name would never match the baked SHA and would re-download the ~1.5GB
AppImage on every check forever, never reaching idle. Add isCommitSha()
(pure, unit-tested) and have checkNowLinux() surface an error instead of
entering that loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Byron Gamatos <xasiklas@gmail.com>

---------

Signed-off-by: Byron Gamatos <xasiklas@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Byron Gamatos <xasiklas@gmail.com>
2026-07-19 12:15:55 +02:00
Jafz2001andClaude Opus 4.8 9388eb248f Open external links via Steam overlay browser on Steam Deck
Steam Deck / Big Picture (gamepad UI) has no registered default web browser,
so shell.openExternal(httpUrl) -> xdg-open falls back to the KDE Discover store
(a Firefox-install prompt) instead of opening the page. This broke 'Connect with
tone3000' and every external link on the Deck.

When RUNNING_UNDER_STEAM_GAMEPAD_UI (linux + SteamGamepadUI/SteamDeck env),
route web links through Steam's overlay browser via steam://openurl/, with a
fallback to the normal OS opener. Desktop platforms are unaffected. The tone3000
OAuth callback redirects to 127.0.0.1 on the device, so auth must complete in an
on-device browser — the Steam overlay browser reaches localhost.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:26:17 -04:00
Byron GamatosandGitHub 4dc68a2a28 fix(main): a failing GPU degrades to software, it does not kill the app (#109)
Addon CI / addon (arm64, macos-14, mac) (push) Has been cancelled
Addon CI / addon (x64, ubuntu-22.04, linux) (push) Has been cancelled
Addon CI / addon (x64, windows-latest, win) (push) Has been cancelled
Ship CI / CI (push) Has been cancelled
When the GPU process can't launch or keeps crashing, Chromium's default is to
give up and FATAL-abort the whole browser process — the user sees the app vanish
("GPU process isn't usable. Goodbye."). We hit this repeatedly: a machine with a
flaky GPU stack took the app down mid-song, and it is the likely reason behind
the "gig always defaults to the classic 2D highway" reports — those machines are
one GPU hiccup away from a crash, not just a fallback.

--disable-gpu-process-crash-limit tells Chromium to keep the browser alive and
fall back to software rendering instead of aborting. Software rasterization
stays enabled (we never pass --disable-software-rasterizer), so there is a path
to land on. The 3D highway then degrades to 2D / runs slow under SwiftShader —
far better than the whole app dying. Cross-platform, set before whenReady.

Validated against a build that reliably FATAL-crashed within seconds on GPU
process launch failure: with the switch it stayed alive 40s+ and never hit the
fatal path — Chromium fell back instead of aborting.

Also adds child-process-gone / render-process-gone log handlers: now that the
app SURVIVES a dead GPU, that log is the only remaining signal it happened,
which is exactly what a "highway is 2D / app was crashing" report needs to be
diagnosable. Log-only.

typecheck + lint clean.
2026-07-15 12:59:47 +02:00
byrongamatos 45a05b80a0 Fix fullscreen launch with maximized restore 2026-07-13 14:26:21 +02:00
gionnibgud 4f1b64ff73 feat: add "start in fullscreen" window preference
Adds an opt-in preference that launches the main window in fullscreen,
driven by feedBack core's Settings → System "Fullscreen" toggle. Wires
the window.feedBackDesktop.window bridge (getStartFullscreen /
setStartFullscreen) that core's setupWindowOptions() gates on, persists
the flag in the desktop config (DesktopConfig.startFullscreen, alongside
windowBounds), and passes `fullscreen: true` at BrowserWindow creation
when set.

Persistence lives here (not renderer localStorage) because the main
process must read the pref at window-creation time. setStartFullscreen
live-applies via setFullScreen so the toggle is responsive on
Windows/Linux; on macOS the first programmatic fullscreen-enter on a
window created windowed is dropped by AppKit, so there it takes effect on
next launch — the core Settings copy notes this. This intentionally
narrows the earlier "never launch fullscreen" default to an opt-in.

Signed-off-by: gionnibgud <gionnibgud@gmail.com>
2026-07-13 13:44:33 +02:00
topkoa 0cde745f03 fix(panes): let toggleWindow be the authority, not a stale hasWindow() check
The tray menu asked "do we own a window for this pane?" and then acted on the
answer. Windows are destroyed asynchronously, so between the question and the
act the answer can go stale: hasWindow() says yes, the window is destroyed,
toggleWindow() returns false, the handler has already committed to the
main-process path and returns — and the click lands on nothing.

A tray item that silently does nothing is the worst possible failure here,
because the tray IS the recovery path when a pane is out of sight.

toggleWindow() already reports whether it did anything. Use that: if it toggled,
we're done; if it didn't, we never had that window (or just lost it), and only
the renderer can decide what opening the pane means — it might belong in the
dock, and its element lives there.

hasWindow()/hasPaneWindow() existed only to support the racy check, so they're
gone rather than left lying around for someone to reintroduce the race with.

Signed-off-by: topkoa <topkoa@gmail.com>
2026-07-12 21:30:38 -04:00
topkoa 3d9e8481ea fix(panes): break the tray/host import cycle; debounce saves; restore before show
Five findings from CodeRabbit on #103.

1. CIRCULAR IMPORT. pane-tray imported pane-hosts while pane-hosts imported
   pane-tray. In the main process that is not a style question: whichever module
   loses the load race sees the other's exports half-initialised, and it fails at
   whatever moment the graph happens to resolve in — which is to say, not on your
   machine.

   One direction only now: pane-hosts → pane-tray. What the tray needs from the
   host (toggle/showAll/hideAll/hasWindow/getMainWindow) is INJECTED through
   initTray(), wired in main.ts.

2. SYNCHRONOUS DISK WRITES ON EVERY DRAG FRAME. `save()` was wired straight to
   'moved'/'resized', and setDesktopConfig is writeFileSync + renameSync. On
   macOS that is dozens of blocking writes per second, in the main process,
   while the user drags.

   Debounced to 400ms — and then flushed on 'close', because a debounce that
   drops the last move is worse than no debounce: nudge a pane, close it a moment
   later, and you would lose the position you just chose, which is the exact thing
   remembered geometry exists to prevent. 'close' (not 'closed') because the
   window has to still exist to be measured.

3. A MINIMIZED PANE COULD NOT BE BROUGHT BACK. Panes go to the tray by being
   minimized and then hidden — and hiding a minimized window does not un-minimize
   it. So show() from the tray restored a window that was still minimized:
   present, but not on screen. Which reads as the tray being broken. Everything
   now goes through reveal(), which restores first.

4. PROTOTYPE POLLUTION VIA PANE ID. A pane id arrives from the RENDERER (it is
   the tail of the frame name window.open() supplied) and is used as a KEY in the
   persisted paneWindows map. `__proto__` is not an id, it is a way to mutate
   Object.prototype from a plugin. Rejected on write, the map is rebuilt on a
   null-prototype object, and reads are own-property checks — otherwise a polluted
   or hand-edited config hands back geometry for a pane that was never saved.

5. Removed getMainWindowRef(), exported and referenced nowhere.

Not applicable: the finding about req.width/req.height/req.title reaching
BrowserWindow unvalidated. That was the `pane:open` IPC, which no longer exists —
main does not create pane windows at all now (the renderer must, so it can adopt
its element into them). The sizes now come from the window Electron already made.

Signed-off-by: topkoa <topkoa@gmail.com>
2026-07-12 21:05:11 -04:00
topkoa 938ddded60 feat(panes)!: dress the renderer's pane window, don't create it
Follows the core change: a pane is now the plugin's REAL panel element,
moved into the pop-out window and still running the plugin's own code
(got-feedback/feedback#928).

That forces one thing here, and it is worth being loud about it:

  WE MUST NOT CREATE THE PANE WINDOW.

To move a live DOM node into another window, the renderer needs a handle on
that window's document. A BrowserWindow we construct in the main process
gives it no such handle. So the renderer opens the window itself with
window.open(), Electron's setWindowOpenHandler turns that into a real
BrowserWindow anyway, and we recognise it in did-create-window by the frame
name the renderer gave it (`fbpane-<paneId>`) and attach the OS behaviour:
remembered bounds, off the taskbar, minimize-to-tray, listed in the tray.

Create the window here instead and the whole feature collapses back into
"reimplement the panel in the pop-out and sync it over IPC" — which is
exactly what we just deleted.

The IPC surface shrinks to two channels, because main never creates or
destroys a pane window and never looks inside one:

  pane:sync    renderer → main   the registry, so the tray can list panes
  pane:toggle  main → renderer   the tray asking for a pane; only the
                                 renderer knows what opening one means (it
                                 might belong in the dock, and its element
                                 lives there)

Gone: pane:open, pane:close, pane:focus, pane:setAlwaysOnTop, pane:closed.
The renderer holds the WindowProxy for a window it opened, so it already
knows when the user closes it — and it has to, because its element is inside
and must be brought home.

Signed-off-by: topkoa <topkoa@gmail.com>
2026-07-12 18:14:49 -04:00
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
OmikronApexandClaude Fable 5 bcbb0e329e fix(main): allow getDisplayMedia through the media permission handler
getDisplayMedia (renderer-bus whole-app loopback capture) rides the
'media' permission request with EMPTY mediaTypes. The audio-only rule
denied it, so the display-media handler never ran and exclusive/ASIO
output lost all page audio (song previews, element-song fallback).

Allow media requests unless they explicitly ask for 'video' (camera
stays blocked; getDisplayMedia video is the app's own frame), mirror
the policy in the permission-check handler, and add [asio-diag] logs
to every deny path plus the display-media handler so future denials
name their stage in tester logs.

Verified packaged: display-media granted → renderer-bus engaged,
engine busEnabled/busFlowing=true on ASIO.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 01:44:06 +02:00
c71aa7c82f feat(audio): ASIO/exclusive — renderer-bus in streamer mix, loopback plumbing, cache clear (#98)
* feat(audio): renderer-bus in streamer mix + whole-app loopback plumbing

Two tester-confirmed gaps under ASIO/exclusive output:

1. Streamer mix carried guitar only when the song rode the renderer bus:
   composeAndPushStreamMix mixed guitar + native backing, never the bus.
   The bus ring is single-consumer, so the consumer step is reworked from
   mixRendererBusInto (drain+add) to pullRendererBus (drain once into a
   fixed scratch); both output callbacks then share the pulled block
   between the device output and the stream submix (rides includeBacking
   — it IS song audio).

2. Previews/UI sounds bypass the per-surface feeder taps entirely and
   leak to the default WASAPI device (audible under ASIO, which doesn't
   silence that endpoint). New plumbing lets the static bundle capture
   ALL app audio: setDisplayMediaRequestHandler answers with this
   window's own frame as audio source (frame-scoped — no other apps'
   audio), plus audio:setPageMuted IPC + preload setPageMuted() as the
   local-silence fallback when suppressLocalAudioPlayback is unsupported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(diag): engine health metrics on every [asio-diag] line

Tester symptom: all audio dead after stopping a song with tones active.
The snapshot showed routing state but not whether the engine was still
producing. Append volatile fields (outside change-detection): in/out/
backing levels, bus fill, input overflows, output underflows, split-ring
fill — outputLevel≈0 with running=true is the silent-engine signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(main): clear Chromium HTTP cache before first load

Testers hop between portable builds sharing one userData dir; an older
build's server sent no Cache-Control, so its cached /static/app.js
outlived it and silently replaced the new build's renderer code (the
fix14 'watcher never installed' log). One cheap clearCache() per launch
makes stale-bundle states impossible even against old-server caches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 18:22:30 +02:00
6349ed4c5f feat(window): persist main window size/position across launches (#97)
Addon CI / addon (arm64, macos-14, mac) (push) Waiting to run
Addon CI / addon (x64, ubuntu-22.04, linux) (push) Waiting to run
Addon CI / addon (x64, windows-latest, win) (push) Waiting to run
Ship CI / CI (push) Waiting to run
* feat(window): persist main window size/position across launches

The main window always opened at a fixed 1400x900, forcing a manual
resize every session. Save the window geometry (normal bounds +
maximized flag) to the existing desktop prefs store on close, and
restore it in createWindow.

Saved bounds are validated by a pure sanitizer against the current
display layout before use, so stale state degrades safely instead of
producing an off-screen or absurd window:
- garbage/partial config -> 1400x900 centered defaults
- size clamped between the 800x600 window minimums and the largest
  display's workArea
- position kept only when the window overlaps a display by at least
  100x50 px (unplugged monitor / resolution change -> re-center);
  negative multi-monitor coordinates remain valid
- maximized sessions save getNormalBounds() and re-maximize on
  restore; fullscreen deliberately restores windowed

No new dependency; reuses get/setDesktopConfig (atomic write,
fail-soft) in soundfont-manager.ts. The store file is already in the
reset-app-settings delete-set, so a config reset also resets bounds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: gionnibgud <gionnibgud@gmail.com>

* fix(window): don't crash shutdown if bounds persistence write fails

The close listener called setDesktopConfig synchronously with no error
handling; a disk-full or permissions failure during the write would throw
unhandled inside the close handler, risking a shutdown crash. Wrap the
write in try/catch and log a warning instead. Flagged by CodeRabbit on PR #97.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: gionnibgud <gionnibgud@gmail.com>

---------

Signed-off-by: gionnibgud <gionnibgud@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:41:47 +02:00
OmikronApexandClaude 0645ce724d fix(mac): add diagnostic logging to ensureMicrophoneAccess TCC flow
Every decision point in the macOS microphone permission path now logs its
status and context to the debug log, including the previously silent
'granted' early-return path — which is the prime suspect for the stale-
grant bug (TCC reports 'granted' for a signature-keyed entry that no
longer matches the running binary).

Also logs:
- platform gate skip
- app.isPackaged === false skip (and why it matters re: NSMicrophoneUsageDescription)
- getMediaAccessStatus return value in all branches
- app identity (name, version, exe path) on 'granted' early return
- askForMediaAccess result + extra warning on user denial
- full error stack on exceptions (not just the message)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-09 23:54:20 +02:00
dd7ad9b786 feat(update): add nightly Velopack update channel (#80)
Addon CI / addon (arm64, macos-14, mac) (push) Waiting to run
Addon CI / addon (x64, ubuntu-22.04, linux) (push) Waiting to run
Addon CI / addon (x64, windows-latest, win) (push) Waiting to run
Ship CI / CI (push) Waiting to run
Adds `nightly` as a selectable auto-update channel (Windows + macOS).

Client:
- update-manager.ts: add 'nightly' to UpdateChannel (veloChannel already
  yields win-x64-nightly / osx-arm64-nightly, so no logic change)
- main.ts: allow 'nightly' in the runtime IPC channel guard
- preload.ts: add 'nightly' to the preload-local UpdateChannel union
- screen.js / settings.html: add the Nightly option + helper text

CI (nightly.yml):
- derive <pkg>-nightly.<UTC date> version in the setup job
- setup-dotnet (pinned from .build-config.json) so the vpk CLI has net8,
  matching build.yml
- vpk pack win-x64-nightly / osx-arm64-nightly (mac signed + notarized,
  mirroring build.yml's signed/unsigned fallback)
- publish a rolling `nightly` GitHub Release (prerelease=false, latest=false)
  that the in-app updater reads for the nightly channel
- concurrency guard so an overlapping dispatch can't race the rolling release

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 00:10:03 +02:00
dcfa6be19d fix(menu): make View → Zoom In accept the unshifted Ctrl+= key (#55)
The app relied on Electron's default application menu, whose View → Zoom In
binds only `CommandOrControl+Plus`. On US / most keyboard layouts "+" is the
shifted form of `=`, so pressing Ctrl with the unshifted +/= key sends Ctrl+=
and nothing happened — while Zoom Out (`Ctrl+-`, no Shift) worked, making zoom
feel half-broken.

Install an explicit application menu that mirrors Electron's default via
role-based submenus and hand-builds only View, where Zoom In also accepts
`Ctrl+=` and numpad `+` (hidden sibling items keep `Ctrl+Shift+=` and numpad
working). Strictly additive — no other menu behaviour changes.


Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF

Co-authored-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:58:10 +02:00
3eefa3646a Force high-performance GPU on Windows to stabilize 3D Highway resolution (#53)
On hybrid-GPU Windows laptops (Intel iGPU + NVIDIA/AMD dGPU), Chromium's
GPU-process adapter selection is non-deterministic across launches. When
it binds the iGPU, the 3D Highway's per-frame WebGL cost blows the draw
budget and the load-adaptive resolution scaler (feedBack#654) silently
drops the canvas to as low as quarter-res — so the highway renders
pixelated even with Quality pinned at HD, varying launch to launch. The
renderer's `powerPreference: 'high-performance'` WebGL hint is only
advisory and doesn't reliably override the OS/Chromium adapter choice.

Append the Chromium `force_high_performance_gpu` switch on win32 (before
app.whenReady, so it's read during Chromium init) so the discrete adapter
is selected consistently and the scaler rarely engages. Single-GPU
machines are unaffected; on dual-GPU desktops it likewise picks discrete.

Fixes #52

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:18:34 +02:00
59e1c1cb0e fix(preload): expose desktop bridge as window.feedBackDesktop (#40)
* fix(preload): expose desktop bridge as window.feedBackDesktop

The core feedback app reads window.feedBackDesktop, but the desktop
preload exposed the bridge as window.slopsmithDesktop. On the desktop
build window.feedBackDesktop was therefore undefined: the DLC-folder
Browse button stayed hidden in both the first-run wizard
(#v3-ob-songdir-browse) and Settings (#btn-pick-dlc), and the rest of the
bridge silently fell back to browser mode.

Finish the rebrand: rename the exposed global slopsmithDesktop ->
feedBackDesktop, plus the internal api object, the renderer +
plugin-manager consumers, the private __feedBackDesktopAudioHooks scratch
namespace, and the comments/migration doc. No compatibility alias — the
ecosystem moves to the new name (TARGET-CURRENT).

Plugins that still read window.slopsmithDesktop are renamed in their own
PRs; nothing ships until the next desktop build bundles them together, so
there is no broken shipped artifact.

Fixes the "Select DLC Songs Folder — No Browse" report (wizard + Settings,
Mac + Windows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF

* fix(preload): also expose bridge under legacy slopsmithDesktop name

Keep plugins/community code built against the pre-rename bridge working
after the rename. Same isMainFrame gating. See got-feedback/feedBack-desktop#41.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: ChrisBeWithYou <chris@rifflarr.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
2026-06-27 13:16:03 +02:00
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
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
topkoaandClaude Opus 4.8 9d424ece9d Rebrand splash/loading dialog Slopsmith -> fee[dB]ack
The startup splash window and its initial status message still showed
the old "Slopsmith" name. Update the brand label, the static status
line, and the JS fallback in splash.html, plus the main-process startup
status snapshot that overrides it, to the new "fee[dB]ack" branding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: topkoa <topkoa@gmail.com>
2026-06-19 08:56:26 -04:00
byrongamatosandClaude Opus 4.8 b5784c20bb Repoint dead slopsmith URLs -> got-feedback
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:02:09 +02:00
Byron Gamatos bd603184d5 Clean release snapshot 2026-06-16 18:48:12 +02:00