# Spellcheck Implementation in Fluxer's Electron Renderer: IPC Architecture and Native Integration

> Discover Fluxer's spellcheck implementation leveraging Electron's native API via a secure IPC bridge for sandboxed, type-safe OS spelling features.

- Repository: [Fluxer/fluxer](https://github.com/fluxerapp/fluxer)
- Tags: internals
- Published: 2026-03-17

---

**Fluxer's spellcheck implementation uses Electron's native spell-checker API orchestrated through a secure IPC bridge between the main process and renderer preload script, enabling type-safe, sandboxed access to OS-level spelling features.**

The **fluxerapp/fluxer** repository implements a robust spell-checking system that leverages Electron's `Session` API while maintaining strict process isolation. This architecture allows the React-based UI to control spell-check settings and handle context-menu suggestions without direct access to Node.js or native APIs. The implementation spans the main process manager, preload script bridge, and renderer consumption patterns.

## Main Process Spellcheck Core

The main process owns all spell-check state and native interactions in [`fluxer_desktop/src/main/Spellcheck.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/Spellcheck.tsx). This file manages the Electron `Session` object, handles IPC requests from the renderer, and emits context-menu events for misspelled words.

### State Definition and Defaults

The system uses a centralized `SpellcheckState` interface to track enabled status and active languages:

```typescript
// Lines 24-27 in Spellcheck.tsx
interface SpellcheckState {
  enabled: boolean;
  languages: string[];
}

```

The `defaultState` enables spell-check by default but starts with an empty language array, allowing the system to fall back to automatic detection:

```typescript
// Lines 34-37 in Spellcheck.tsx
const defaultState: SpellcheckState = {
  enabled: true,
  languages: [],
};

```

### Session Configuration and Language Detection

The `pickSystemLanguages` function (lines 60-79) intelligently matches the OS-preferred locales against `session.availableSpellCheckerLanguages`, ensuring the spell-checker uses the most appropriate dictionaries without manual configuration.

The `applyStateToSession` function (lines 82-94) synchronizes the internal state to Electron's native APIs:

```typescript
// Lines 82-94 in Spellcheck.tsx
const applyStateToSession = (session: Session, state: SpellcheckState) => {
  session.setSpellCheckerEnabled(state.enabled);
  
  // Platform-specific language handling
  if (process.platform !== 'darwin') {
    session.setSpellCheckerLanguages(state.languages);
  }
};

```

**Note:** On **macOS**, the system spell-checker manages languages automatically, while Windows and Linux require explicit language list configuration via `setSpellCheckerLanguages`.

### IPC Handler Registration

The main process registers six distinct IPC channels to expose spell-check functionality to the renderer:

- **`spellcheck-get-state`**: Returns current enabled status and language list
- **`spellcheck-set-state`**: Updates state and broadcasts changes to all renderers
- **`spellcheck-get-available-languages`**: Exposes supported dictionaries
- **`spellcheck-open-language-settings`**: Launches OS language preferences
- **`spellcheck-replace-misspelling`**: Executes text replacement
- **`spellcheck-add-word-to-dictionary`**: Permanently adds words to user dictionary

These handlers are registered using `ipcMain.handle` (lines 59-89), ensuring promise-based communication with the renderer.

### Context Menu Integration and Safety Guards

The `webContents.on('context-menu')` handler (lines 90-115) intercepts right-click events to build rich context menus containing misspelled words and suggestions. It emits a `textarea-context-menu` event to the renderer with the following payload structure:

```typescript
{
  misspelledWord: string,
  suggestions: string[],
  isEditing: boolean,
  editFlags: {
    canCopy: boolean,
    canPaste: boolean,
    // ... additional edit capabilities
  }
}

```

To prevent stale context menus on non-editable elements, the `ensureContextIpc` function (lines 48-57) tracks the last valid target element through a `spellcheck-context-target` channel, filtering out clicks on non-textarea inputs.

## Renderer-Side Bridge Architecture

The preload script at [`fluxer_desktop/src/preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/preload/index.tsx) creates a type-safe abstraction layer using Electron's `contextBridge`, exposing only the necessary methods to the renderer window.

### Exposed API Contract

Lines 64-87 define the `electron` object available as `window.electron` in the React application:

```typescript
// Excerpt from preload/index.tsx (lines 64-87)
contextBridge.exposeInMainWorld('electron', {
  spellcheckGetState: () => 
    ipcRenderer.invoke('spellcheck-get-state'),
    
  spellcheckSetState: (state: SpellcheckState) => 
    ipcRenderer.invoke('spellcheck-set-state', state),
    
  spellcheckGetAvailableLanguages: () => 
    ipcRenderer.invoke('spellcheck-get-available-languages'),
    
  spellcheckOpenLanguageSettings: () => 
    ipcRenderer.invoke('spellcheck-open-language-settings'),
    
  spellcheckReplaceMisspelling: (replacement: string) => 
    ipcRenderer.invoke('spellcheck-replace-misspelling', replacement),
    
  spellcheckAddWordToDictionary: (word: string) => 
    ipcRenderer.invoke('spellcheck-add-word-to-dictionary', word),
    
  onSpellcheckStateChanged: (callback: (state: SpellcheckState) => void) => {
    const handler = (_event: any, data: SpellcheckState) => callback(data);
    ipcRenderer.on('spellcheck-state-changed', handler);
    return () => ipcRenderer.removeListener('spellcheck-state-changed', handler);
  },
  
  onTextareaContextMenu: (callback: (params: ContextMenuParams) => void) => {
    const handler = (_event: any, data: ContextMenuParams) => callback(data);
    ipcRenderer.on('textarea-context-menu', handler);
    return () => ipcRenderer.removeListener('textarea-context-menu', handler);
  },
});

```

Each method uses `ipcRenderer.invoke` for request-response patterns and `ipcRenderer.on` for event subscriptions, with cleanup functions to prevent memory leaks.

### Context Menu Target Detection

Lines 90-96 implement a lightweight DOM observer that classifies right-click targets before the main process builds the context menu:

```typescript
// Lines 90-96 in preload/index.tsx
window.addEventListener('contextmenu', (event) => {
  const target = event.target as HTMLElement | null;
  const isTextarea = Boolean(target?.closest?.('textarea'));
  ipcRenderer.send('spellcheck-context-target', { isTextarea });
}, true);

```

This listener captures the event during the capture phase (`true` parameter), ensuring the main process knows whether to display spell-check suggestions before Electron's default context menu appears.

## UI Consumption Patterns

Renderer components interact with the spell-check system through the typed `window.electron` API. The [`fluxer_app/src/types/electron.d.ts`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/types/electron.d.ts) file provides TypeScript declarations for autocomplete support.

### React Hook Implementation

A typical implementation fetches initial state and subscribes to updates:

```typescript
import { useEffect, useState } from 'react';

export function useSpellcheck() {
  const [enabled, setEnabled] = useState(true);
  const [languages, setLanguages] = useState<string[]>([]);
  const [suggestions, setSuggestions] = useState<string[]>([]);

  // Fetch initial state and subscribe to changes
  useEffect(() => {
    const fetchState = async () => {
      const state = await window.electron.spellcheckGetState();
      setEnabled(state.enabled);
      setLanguages(state.languages);
    };
    fetchState();

    return window.electron.onSpellcheckStateChanged((state) => {
      setEnabled(state.enabled);
      setLanguages(state.languages);
    });
  }, []);

  // Handle context menu for misspelled words
  useEffect(() => {
    return window.electron.onTextareaContextMenu((params) => {
      if (params.misspelledWord) {
        setSuggestions(params.suggestions);
        // Trigger custom suggestion UI...
      }
    });
  }, []);

  const toggleSpellcheck = async () => {
    const newState = await window.electron.spellcheckSetState({ 
      enabled: !enabled,
      languages 
    });
    setEnabled(newState.enabled);
  };

  const replaceWord = (replacement: string) => {
    window.electron.spellcheckReplaceMisspelling(replacement);
  };

  return { enabled, languages, suggestions, toggleSpellcheck, replaceWord };
}

```

This pattern ensures the UI stays synchronized with the main process state while handling platform-specific behaviors like macOS automatic language detection.

### Platform-Specific Language Settings

The `spellcheckOpenLanguageSettings` method provides native OS integration:

- **macOS**: Opens System Preferences > Keyboard > Text (using AppleScript or `shell.openExternal`)
- **Windows**: Launches Windows Settings > Time & Language > Language

This avoids implementing custom language configuration UIs while respecting system-wide spelling preferences.

## Summary

Fluxer's spellcheck implementation demonstrates secure Electron architecture through three key layers:

- **Main Process Ownership**: The [`Spellcheck.tsx`](https://github.com/fluxerapp/fluxer/blob/main/Spellcheck.tsx) module exclusively controls the Electron `Session` spell-checker, managing state persistence and OS-native dictionaries.
- **Preload Script Isolation**: The [`preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/preload/index.tsx) bridge exposes only necessary IPC methods via `contextBridge`, preventing direct renderer access to Node APIs.
- **Renderer Abstraction**: React components consume a typed, promise-based API with event subscriptions for real-time state synchronization and context-menu handling.

The system supports dynamic language switching, platform-optimized settings integration, and secure text replacement workflows while maintaining strict process separation between the main and renderer processes.

## Frequently Asked Questions

### How does Fluxer handle spell-check language detection on macOS versus Windows?

On **macOS**, Fluxer's `applyStateToSession` function (lines 82-94) skips explicit language configuration because the OS manages spell-check languages system-wide through the native `Session` API. On **Windows** and **Linux**, the implementation explicitly calls `session.setSpellCheckerLanguages()` with the user-selected array from `SpellcheckState`, requiring manual language management through the IPC-exposed `spellcheckGetAvailableLanguages` channel.

### What IPC channels are available for spell-check operations?

The main process registers six specific channels in [`Spellcheck.tsx`](https://github.com/fluxerapp/fluxer/blob/main/Spellcheck.tsx) (lines 59-89): `spellcheck-get-state`, `spellcheck-set-state`, `spellcheck-get-available-languages`, `spellcheck-open-language-settings`, `spellcheck-replace-misspelling`, and `spellcheck-add-word-to-dictionary`. Additionally, the renderer receives updates via the `spellcheck-state-changed` event and context-menu data through `textarea-context-menu`.

### How does the preload script prevent security vulnerabilities?

The preload script uses Electron's `contextBridge.exposeInMainWorld` to create a carefully curated API surface (lines 64-87), exposing only specific spell-check methods rather than the full `ipcRenderer`. This **context isolation** ensures renderer code cannot arbitrarily invoke main process functions or access Node.js APIs, mitigating XSS and prototype pollution attacks while maintaining functionality.

### Why does the context menu implementation track textarea elements separately?

The `contextmenu` listener in [`preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/preload/index.tsx) (lines 90-96) sends a `spellcheck-context-target` message to filter events before the main process builds the context menu. This allows `ensureContextIpc` (lines 48-57) to validate that the right-click occurred on an editable textarea rather than password fields or static content, ensuring spell-check suggestions only appear for appropriate input targets.