fix: restore dropped IPC handlers and fix several main-process bugs - #288
Closed
devoliverluccas wants to merge 3 commits into
Closed
fix: restore dropped IPC handlers and fix several main-process bugs#288devoliverluccas wants to merge 3 commits into
devoliverluccas wants to merge 3 commits into
Conversation
…red up Both `native/autoLaunch.ts` and `native/badges.ts` registered their IPC handlers as an import side effect, and nothing imported either module, so the handlers did not exist at runtime. For autostart this was a regression: - e00f3a8 (stoatchat#237) removed the last direct use of `autoLaunch`, leaving the import unused - 6894231 (stoatchat#252) then removed the now-unused import during the lint pass `ipcMain.handle("getAutostart"/"setAutostart")` went with it, so the renderer's `desktopConfig.setAutostart()` rejects with "No handler registered" and the toggle in settings silently does nothing. Closes stoatchat#287. Badges were never wired up at all -- `git log -S 'native/badges'` shows the module has not been imported since the initial commit, even though the preload bridge has exposed `native.setBadgeCount()` since stoatchat#25. Refs stoatchat#213. Both modules now expose an explicit `init*()` that `main.ts` calls, so the registration is tied to a call the linter cannot quietly drop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Lucas Oliveira <lucasferreiradeoliveira.20@gmail.com>
Two separate problems kept the RPC presence alive after it was turned off, and kept it from coming back on without a restart. Refs stoatchat#203. Turning it on did nothing: the `discordRpc` setter ran `initDiscordRpc()` *before* writing the new value to the store, and `initDiscordRpc` reads the setting back out of that store. It therefore observed the previous value, took the `if (!config.discordRpc) return` early exit, and gave up. The setters now persist first and act second. Turning it off left the old client running: `initDiscordRpc` only called `removeAllListeners()` on the previous client and never destroyed it, so every toggle stacked another client that stayed logged in to Discord and kept broadcasting the activity. Teardown now detaches the listeners, destroys the client and cancels any pending reconnect. Also fixes the reconnect path around it: - `login()` is asynchronous, so its rejection escaped the `try`/`catch` as an unhandled rejection and never triggered a retry; it is awaited now - `destroy()` rejects when the transport never connected, which is now swallowed instead of surfacing as an unhandled rejection - `disconnected` only schedules a reconnect for the client that is still current, so a client being torn down cannot resurrect itself - reconnects no longer stack when several fire before the timer elapses Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Lucas Oliveira <lucasferreiradeoliveira.20@gmail.com>
…on screen Three unrelated main-window bugs. Spellchecker (refs stoatchat#69): `webPreferences.spellcheck` was hardcoded to `true` and `setSpellCheckerEnabled` only ever ran from the setter, so the stored preference was never applied at startup and disabling it silently reverted on the next launch. Screen picker (refs stoatchat#267, stoatchat#263): the `screenPickerCallback` listener is registered with `ipcMain.once`, so a picker the user dismissed without choosing a source left it attached forever. The next screen share request registered a second listener behind the stale one, which then consumed the reply and handed it to a request that was already dead -- leaving screen sharing broken until the app restarted. Stale listeners are now cleared before registering, mirroring what the renderer side already does in `world/window.ts`. Alongside it: - the bounds check rejected `idx < 0 || idx > sources.length`, so `idx === sources.length` passed and `sources[idx]` handed `undefined` to the callback as if it were a real source - `getSources()` had no rejection path, so a failure left the renderer waiting on a callback that was never invoked - `NativeImage.resize()` returns a new image instead of mutating in place, so discarding the result sent full-size icons over IPC Window position (refs stoatchat#176): the saved position was restored without checking it still lands on a connected display, so unplugging or rearranging a monitor left the window opening off screen with no way to reach it. It now falls back to centring the window. The guard also changed from `x > 0 || y > 0` to `x !== 0 || y !== 0`, since negative coordinates are legitimate for displays positioned left of or above the primary one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Lucas Oliveira <lucasferreiradeoliveira.20@gmail.com>
devoliverluccas
force-pushed
the
lucas-oliveira/fix-critical-bugs
branch
from
August 19, 2026 21:42
9c44642 to
5743d18
Compare
Member
|
for-desktop follows the same contribution guidelines as for-web. We do not accept contributions made by LLMs. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Six main-process bugs, three of which have open issues that turn out to have the same root cause: an IPC handler that is not registered at runtime.
1. Autostart and badge IPC handlers were never registered
native/autoLaunch.tsandnative/badges.tsregister their handlers as an import side effect, and nothing imports either module.For autostart this is a regression in 1.5.0:
autoLaunch, leaving the import unusedipcMain.handle("getAutostart"/"setAutostart")went with it, sodesktopConfig.setAutostart()rejects with "No handler registered" and the settings toggle silently does nothing — matching #287 exactly ("I had removed the option for Stoat to auto start in the options menu. However it still auto started. I verified that the windows startup option was still ticked").Badges were never wired up at all:
git log -S 'native/badges'shows the module has not been imported since the initial commit, even though the preload bridge has exposednative.setBadgeCount()since #25.Verifiable on the current
main:Both modules now expose an explicit
init*()called frommain.ts, so registration is tied to a call site the linter cannot quietly drop.Closes #287. Refs #213 — this only restores the plumbing; the UX described in that issue still needs client-side work.
2. Discord RPC (refs #203)
discordRpcsetter raninitDiscordRpc()before writing the new value to the store, andinitDiscordRpcreads that setting back out of the store — so it observed the stale value and took theif (!config.discordRpc) returnearly exit. The setters now persist first and act second.initDiscordRpconly calledremoveAllListeners()on the previous client and never destroyed it, so every toggle stacked another client that stayed logged in to Discord and kept broadcasting the activity.login()is asynchronous, so its rejection escaped thetry/catchas an unhandled rejection and never triggered a retry.destroy()rejects when the transport never connected, which is now swallowed rather than surfacing as an unhandled rejection.disconnectedonly schedules a reconnect for the client that is still current, and reconnects no longer stack.3. Spellchecker not applied on launch (refs #69)
webPreferences.spellcheckwas hardcoded totrueandsetSpellCheckerEnabledonly ever ran from the setter, so the stored preference was never applied at startup — disabling it silently reverted on the next launch.4. Screen sharing breaks after a cancelled picker (refs #267, #263)
screenPickerCallbackis registered withipcMain.once, so a picker the user dismissed without choosing a source leaves it attached forever. The next screen share request registers a second listener behind the stale one, which then consumes the reply and hands it to a request that is already dead — leaving screen sharing broken until the app restarts. Stale listeners are now cleared before registering, mirroring what the renderer side already does inworld/window.ts.Alongside it:
idx < 0 || idx > sources.length, soidx === sources.lengthpassed andsources[idx]handedundefinedto the callback as if it were a real sourcegetSources()had no rejection path, so a failure left the renderer waiting on a callback that was never invokedNativeImage.resize()returns a new image instead of mutating in place, so discarding the result sent full-size icons over IPC5. Window restored onto a disconnected display (refs #176)
The saved position was restored without checking that it still lands on a connected display, so unplugging or rearranging a monitor left the window opening off screen with no way to reach it. It now falls back to centring the window.
The guard also changes from
x > 0 || y > 0tox !== 0 || y !== 0, since negative coordinates are legitimate for displays positioned left of or above the primary one.Testing
mise lint,mise formatandmise buildall pass.I have not exercised these interactively — no manual autostart, screen share or RPC run — so runtime confirmation from someone on each platform would be welcome, particularly the screen picker path. The autostart and badge handlers are confirmed by diffing the packaged bundle before and after (0 → 1 occurrences of each handler, plus the 12 badge assets now resolved).
🤖 Generated with Claude Code