Skip to content

Conversation

@imprisonedmind
Copy link

@imprisonedmind imprisonedmind commented Nov 6, 2025

Summary

  • Introduce Sonner-based toast helpers so plugins can surface dependency or status feedback to users directly in the Backslash UI.
  • Offer a search.clear() helper that plugin authors can call when they want the command input reset after success (similar to Raycast’s behaviour).
  • Share a lightweight event registry that connects current and future plugin->renderer channels without repeating webContents.send boilerplate.

Motivation

  • Toasts: Some plugin commands depend on external binaries (e.g., playerctl). When that dependency is missing, we currently fail silently. By exposing an optional toast helper, plugins can gently guide users to install the prerequisite with rich inline messaging.
  • Search clearing: Several command launchers clear their search input after an immediate action. Providing an opt-in helper lets Backslash plugin authors adopt that familiar UX pattern without forcing it globally.
  • Registry: Both features required almost identical emitter wiring. A registry keeps the main process tidy and makes it easier to add more plugin->UI signals later.

What Changed

Plugin toast support

  • Isolated branch: https://github.com/imprisonedmind/backslash/tree/feature/sonner-error
  • Added src/main/pluginToast.ts with a guarded emitter and convenience helpers (toast.show, .success, .error, etc.).
  • Renderer listens via winElectron.onPluginToast, piping events to Sonner in usePluginToasts.
  • Preload + type definitions updated to expose onPluginToast.
  • Documentation now shows plugin authors how to call the helper:
    module.exports = {
      commands: {
        'demo-command': {
          run: async (_, { toast }) => {
            try {
              await doSomethingThatNeedsDeps()
              toast.success('Done!', { description: 'Everything worked.' })
            } catch (error) {
              toast.error('Missing dependency', { description: error.message })
            }
          }
        }
      }
    }

Plugin search clearing helper

  • Isolated branch: https://github.com/imprisonedmind/backslash/tree/feature/clear-search
  • Added src/main/pluginSearch.ts and corresponding renderer hook usePluginSearch.
  • Plugins now receive a search helper in their dependency bag; calling search.clear() tells the UI to reset the command input.
  • Player-ctl plugin updated (in companion repo) to clear search after playback actions.
  • Docs highlight the pattern for plugin authors:
    module.exports = {
      commands: {
        'demo-command': {
          run: async (_, { search }) => {
            await doSomething()
            search.clear()
          }
        }
      }
    }

Shared event registry

  • New src/main/pluginEvents.ts keeps a registry of { channel, register } pairs and routes payloads to whichever window is active.
  • src/main/index.ts now calls registerPluginEmitters(() => mainWindow ?? null) instead of hand-wiring each emitter.

Screenshots / GIFs

show_sonner_small
Show Sonner

show_search_clear
Clear Search

@imprisonedmind imprisonedmind changed the title Feature: plugin to UI communication Plugin to UI communication channel, present Sonner toast on failure, clear search on success Nov 6, 2025
@imprisonedmind
Copy link
Author

imprisonedmind commented Nov 6, 2025

There's likely a better solution to clear search as a primary function, where on successfully doing something based on the content of the search box it then clears. opening apps, running commands. This is a pattern Raycast uses, and I am very used to not having to backspace the command bar.

I've implemented it as opt-in, as I did not want to introduce any major changes to the UX.

You can see how useful it is in this demo when quickly chaining commands:
https://youtu.be/iZjdRQBSmQM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant