Skip to content

Cross-Platform Development Guide

Tronbun supports macOS and Windows. This guide covers platform differences, best practices, and common pitfalls.

Supported Platforms

PlatformWebview EngineMin VersionStatus
macOSWebKit (WKWebView)10.15 (Catalina)Full support
WindowsWebView2 (Chromium)Windows 10Full support
LinuxWebKitGTKExperimental

Architecture Overview

Tronbun's native layer uses platform-specific implementations behind a common C interface:

webview/
├── platform/
│   ├── platform_window_macos.mm    # macOS window (Objective-C++)
│   ├── platform_window_win.c       # Windows window
│   ├── platform_window_linux.c     # Linux window
│   ├── platform_tray_macos.mm      # macOS tray
│   ├── platform_tray_win.c         # Windows tray
│   └── ...
├── webview_main.c                  # Cross-platform entry point
├── tray_main.c                     # Cross-platform tray entry
└── common/
    └── ipc_common.c                # Shared IPC protocol

Your TypeScript code is fully cross-platform. Platform differences are handled in the native layer.

Platform Differences

Webview Engine

FeaturemacOS (WebKit)Windows (WebView2)
JavaScript ES2022+YesYes
CSS Grid/FlexboxYesYes
Web WorkersYesYes
WebRTCYesYes
Custom protocol (tronbun://)WKURLSchemeHandlerWebResourceRequested
DevToolsSafari-styleChrome-style

Both engines support modern web standards. Differences are rare but can occur with bleeding-edge CSS features or experimental Web APIs.

Window Behavior

FeaturemacOSWindows
Title barUnified styleClassic/Windows 11
TransparencyFull supportLimited (DWM-dependent)
Background blurNSVisualEffectViewAcrylic/Mica
Frameless modeWorksWorks
Always on topWorksWorks
Window opacity0.0-1.00.0-1.0
FeaturemacOSWindows
Application menu barNative (top of screen)Native (in window)
Menu rolesFull support (about, services, etc.)Partial (no macOS-specific roles)
Context menusNativeNative
Keyboard acceleratorsCmd+ shortcutsCtrl+ shortcuts

Use CmdOrCtrl in accelerator strings for cross-platform shortcuts:

typescript
{ accelerator: "CmdOrCtrl+S" }  // Cmd+S on macOS, Ctrl+S on Windows

macOS-specific roles ("services", "hide", "hideOthers", "unhide", "startSpeaking", "stopSpeaking") are ignored on Windows.

Notifications

FeaturemacOSWindows
Permission modelUNUserNotificationCenterToast notifications
Action buttonsSupportedSupported
Dev modeTronbunNotifier.app helperDirect
Compiled modelibnotification.dylib (FFI)libnotification.dll (FFI)

macOS quirk: On macOS 15 (Sequoia) with ad-hoc signed apps, the native permission dialog may not appear. Tronbun handles this by showing a fallback dialog directing users to System Settings.

System Tray

FeaturemacOSWindows
Icon size22x22 (template images)16x16 or 32x32
Icon formatPNG, template imagesICO preferred, PNG
TooltipSupportedSupported
MenuDropdownContext menu

File Dialogs

Both platforms use native file dialogs with the same API. Platform-specific behaviors:

  • macOS: Uses NSOpenPanel / NSSavePanel
  • Windows: Uses IFileOpenDialog / IFileSaveDialog

File filter extensions work the same on both platforms.

Compilation Output

macOSWindows
Format.app bundle.exe + companion files
Code signingAd-hoc (codesign)Not automated
Icon.icns in Resources.ico (requires external tool)
DistributionZip or DMGZip or installer

Best Practices

1. Use Cross-Platform Paths

Bun's path module handles platform differences:

typescript
import { join } from "path";

// Good — works on both platforms
const configPath = join(process.cwd(), "config", "settings.json");

// Bad — macOS-only
const configPath = `${process.cwd()}/config/settings.json`;

2. Use CmdOrCtrl for Shortcuts

typescript
// Good
{ accelerator: "CmdOrCtrl+S" }

// Bad — only works on macOS
{ accelerator: "Cmd+S" }

3. Handle Platform-Specific Icons

json
{
  "app": {
    "icon": "assets/icon.icns",
    "iconWin": "assets/icon.ico"
  },
  "tray": {
    "icon": "assets/tray-icon.png"
  }
}

Provide icons in the correct format for each platform.

4. Detect Platform When Needed

typescript
if (process.platform === "darwin") {
  // macOS-specific behavior
  await win.menu.setMenu([
    { label: "MyApp", items: [{ role: "about" }, { role: "quit" }] },
    ...otherMenus,
  ]);
} else {
  // Windows: no app-name menu needed
  await win.menu.setMenu(otherMenus);
}

5. Test Both Platforms

The same TypeScript code runs on both platforms, but always verify:

  • Window appearance and layout
  • Menu behavior
  • Notification permissions
  • File path handling
  • Native dialog appearance

6. Use CSS for Platform-Specific Styling

css
/* Detect platform via JavaScript and set a class on <body> */
body.platform-darwin {
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}

body.platform-win32 {
  font-family: "Segoe UI", sans-serif;
}

Set the class from your init script:

typescript
this.init(`
  document.body.classList.add("platform-" + "${process.platform}");
`);

Common Pitfalls

Dock Icon on macOS

Compiled apps set LSUIElement = true by default (no dock icon). To show a dock icon, you'll need to modify the generated Info.plist or handle it programmatically.

Windows WebView2 Runtime

Windows users need the WebView2 runtime installed. It's included with Windows 11 and recent Windows 10 updates. For older systems, bundle the Evergreen WebView2 Runtime bootstrapper.

File Path Separators

While Bun handles / on Windows, native file dialogs may return \ paths. Normalize paths when needed:

typescript
const normalizedPath = filePath.replace(/\\/g, "/");

macOS Notification Permissions

Ad-hoc signed apps on macOS 15+ may not trigger the system permission dialog. Always handle the "denied" and "unavailable" states from requestPermission().

See Also

Released under the GPLv3 License.