How Fluxer Implements the Application Menu and Platform-Specific Menu Items

Fluxer builds its cross-platform application menu dynamically at runtime using Electron's Menu.buildFromTemplate in fluxer_desktop/src/main/Menu.tsx, detecting the platform and build channel to conditionally render macOS-specific items like the App menu and standard Edit roles while handling Windows/Linux through a unified template array.

Fluxer is an Electron-based desktop application that requires native-looking menu bars across macOS, Windows, and Linux. The application menu implementation lives in the fluxer_desktop/src/main/Menu.tsx module, where it constructs a platform-aware menu template that adapts labels, roles, and accelerators based on the operating system and build channel.

Runtime Platform Detection

The menu construction begins by detecting the execution environment. In fluxer_desktop/src/main/Menu.tsx, the implementation checks process.platform === 'darwin' to determine if the app is running on macOS, and evaluates BUILD_CHANNEL === 'canary' to adjust the application name dynamically.

// fluxer_desktop/src/main/Menu.tsx
const isCanary = BUILD_CHANNEL === 'canary';
const appName = isCanary ? 'Fluxer Canary' : 'Fluxer';
const isMac = process.platform === 'darwin';

const template: Array<MenuItemConstructorOptions> = [];

These variables control which menu sections are appended to the template array and how labels are generated throughout the menu structure.

Constructing the Menu Template

The createApplicationMenu() function assembles an ordered array of MenuItemConstructorOptions objects, pushing platform-specific sections based on the isMac boolean.

The macOS App Menu

On macOS, Fluxer inserts a dedicated application menu as the first item in the template. This section calls app.setName(appName) to ensure the menu displays the correct branding (stable or Canary), then includes standard macOS roles like about, services, hide, and quit.

if (isMac) {
  app.setName(appName);
  template.push({
    label: appName,
    submenu: [
      { role: 'about', label: `About ${appName}` },
      { type: 'separator' },
      {
        label: 'Preferences...',
        accelerator: 'Cmd+,',
        click: () => {
          const mainWindow = getMainWindow();
          mainWindow?.webContents.send('open-settings');
        },
      },
      { type: 'separator' },
      { role: 'services' },
      { type: 'separator' },
      { role: 'hide' },
      { role: 'hideOthers' },
      { role: 'unhide' },
      { type: 'separator' },
      { role: 'quit' }
    ],
  });
}

Cross-Platform File Menu

The File menu adapts based on the platform. On macOS, it contains only a close role to match Apple Human Interface Guidelines. On Windows and Linux, the File menu includes explicit Preferences and Quit entries since those platforms lack the dedicated App menu.

// File menu structure (simplified)
template.push({
  label: 'File',
  submenu: isMac 
    ? [{ role: 'close' }]
    : [
        {
          label: 'Preferences',
          accelerator: 'Ctrl+,',
          click: () => getMainWindow()?.webContents.send('open-settings')
        },
        { type: 'separator' },
        { role: 'quit' }
      ]
});

Edit Menu with Platform Roles

The Edit menu provides standard text editing actions across all platforms. macOS receives additional roles including pasteAndMatchStyle, delete, and selectAll, along with the Speech submenu for dictation support.

template.push({
  label: 'Edit',
  submenu: [
    { role: 'undo' },
    { role: 'redo' },
    { type: 'separator' },
    { role: 'cut' },
    { role: 'copy' },
    { role: 'paste' },
    ...(isMac ? [
      { role: 'pasteAndMatchStyle' },
      { role: 'delete' },
      { role: 'selectAll' },
      { type: 'separator' },
      { label: 'Speech', submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }] }
    ] : [
      { role: 'delete' },
      { type: 'separator' },
      { role: 'selectAll' }
    ])
  ]
});

View Menu and IPC Communication

The View menu handles zoom controls and developer tools. Zoom actions dispatch IPC messages to the renderer process using getMainWindow().webContents.send(), allowing the UI to update zoom levels without reloading.

{
  label: 'View',
  submenu: [
    { role: 'reload' },
    { role: 'forceReload' },
    { role: 'toggleDevTools' },
    { type: 'separator' },
    {
      label: 'Zoom In',
      accelerator: 'CmdOrCtrl+Plus',
      click: () => getMainWindow()?.webContents.send('zoom-in')
    },
    {
      label: 'Zoom Out',
      accelerator: 'CmdOrCtrl+-',
      click: () => getMainWindow()?.webContents.send('zoom-out')
    },
    {
      label: 'Reset Zoom',
      accelerator: 'CmdOrCtrl+0',
      click: () => getMainWindow()?.webContents.send('zoom-reset')
    },
    { type: 'separator' },
    { role: 'togglefullscreen' }
  ]
}

The Window menu (macOS) and Help menu (all platforms) follow similar patterns, with the Help menu using shell.openExternal to open the website and GitHub repository in the system browser.

Registering the Menu

After assembling the template array, the function builds the menu instance and registers it as the global application menu. This occurs in fluxer_desktop/src/main/index.tsx during the app's whenReady lifecycle event.

// fluxer_desktop/src/main/Menu.tsx
export function createApplicationMenu(): void {
  // ... template construction ...
  const menu = Menu.buildFromTemplate(template);
  Menu.setApplicationMenu(menu);
}

The main entry point invokes this function immediately after the Electron app reaches the ready state:

// fluxer_desktop/src/main/index.tsx
app.whenReady().then(async () => {
  try {
    createApplicationMenu();
  } catch (error) {
    log.error('[Init] Failed to create application menu:', error);
  }
  createWindow();
});

Summary

  • Fluxer generates its application menu at runtime in fluxer_desktop/src/main/Menu.tsx using Electron's Menu.buildFromTemplate.
  • Platform detection relies on process.platform === 'darwin' to conditionally render macOS-specific items like the App menu and extended Edit roles.
  • Build channel awareness changes the application name dynamically between "Fluxer" and "Fluxer Canary" throughout menu labels.
  • IPC communication bridges the main and renderer processes for zoom controls and preferences, using webContents.send() from menu click handlers.
  • Lifecycle integration occurs in fluxer_desktop/src/main/index.tsx, ensuring the menu is built before the main window creation.

Frequently Asked Questions

How does Fluxer handle the macOS App menu differently from Windows?

On macOS, Fluxer creates a dedicated application menu as the first item containing About, Preferences, Services, Hide, and Quit roles, following Apple's Human Interface Guidelines. Windows and Linux omit this menu entirely, moving Preferences and Quit into the File menu instead.

Where does Fluxer detect the platform to build the correct menu?

Platform detection occurs at the start of createApplicationMenu() in fluxer_desktop/src/main/Menu.tsx using const isMac = process.platform === 'darwin'. This boolean determines which menu sections are pushed into the template array.

How do menu items communicate with the renderer process?

Menu items use Electron's IPC mechanism by calling getMainWindow().webContents.send() with specific channel names like 'open-settings', 'zoom-in', or 'zoom-reset'. The renderer process listens for these messages to update UI state without page reloads.

What determines whether the menu shows "Fluxer" or "Fluxer Canary"?

The BUILD_CHANNEL environment variable checked at Menu.tsx:L24 sets the appName variable to either "Fluxer" or "Fluxer Canary". This variable propagates to menu labels, window titles, and the app.setName() call on macOS.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →