Sakura Todo is a React + TypeScript app packaged as a cross‑platform desktop app using Electron. Electron embeds Chromium + Node.js to render your web UI and access desktop capabilities (windowing, file system, menus, tray, shortcuts). In development we load the Vite dev server; in production we load the built static files.
- One codebase, desktop everywhere: ship Windows (.exe), macOS (.dmg/.zip), Linux (.AppImage)
- Web UI + native powers: modern React UI plus OS integrations (menus, shortcuts, file system)
- Fast DX: Vite dev server + hot reload; same UI code runs on web and desktop
electron/main.ts: Electron main process- Frameless window with dark chrome (
frame: false) and custom title bar - IPC handlers for window controls (minimize/maximize/fullscreen/close)
- View menu + global shortcuts for zoom and fullscreen
- Opens external links in the system browser
- Frameless window with dark chrome (
electron/preload.ts: Secure bridge exposed to the renderer viacontextBridge- Exposes
window.desktop.windowcontrols and state subscription helpers
- Exposes
src/components/TitleBar.tsx: Custom dark title bar UI with native-like controls- Minimize, Fullscreen (⤢), Close
- Titlebar double‑click toggles maximize
- Draggable area using
-webkit-app-region
electron/tsconfig.json: Electron-specific TypeScript config (CommonJS output toelectron/dist)electron/package.json: Marks theelectron/subtree as CommonJSelectron-builder.yml: Packaging config forelectron-builderpackage.jsonupdates:main:electron/dist/main.js- Scripts for dev, build, packaging
- Dev deps:
electron,electron-builder,concurrently,wait-on,@types/node
- Development
- Vite runs at
http://localhost:5173 - TypeScript compiles Electron files into
electron/dist(watch) - Electron launches after both Vite and compiled outputs are ready
- Custom TitleBar is rendered by React; window is frameless
- Vite runs at
- Production
vite buildemits static files indist/- Electron main loads
app.getAppPath()/dist/index.html(packaged‑safe) electron-builderpackages the app per OS
- Title bar (renderer):
- Minimize — minimizes (exits fullscreen first if needed)
- Fullscreen (⤢) — toggle fullscreen edge‑to‑edge
- Close — closes the app
- Double‑click title bar — toggle maximize/restore
- Keyboard shortcuts:
- Zoom in: Ctrl/Cmd + (also Shift + =, numpad +)
- Zoom out: Ctrl/Cmd -
- Reset zoom: Ctrl/Cmd 0
- Toggle fullscreen: F11 (and TitleBar button)
- Implementation:
- App menu includes zoom roles and fullscreen
- Global shortcuts registered to catch zoom keys regardless of focus
- Visual zoom limits set to 1–3; default zoom factor is 1
npm run dev— runs Vite, compiles Electron in watch mode, launches Electronnpm run dev:web— Vite dev server onlynpm run dev:electron— Electron watcher + launcher (waits for Vite &electron/dist)npm run build— builds Vite and compiles Electron oncenpm run dist— generates icons and packages the app withelectron-buildernpm run start:electron— launch Electron from compiled output (afterbuild)
./electron/
main.ts # Electron main process; frameless window, IPC, menu, shortcuts
preload.ts # Preload script exposing safe desktop bridge
tsconfig.json # TS config for Electron, outputs to electron/dist (CJS)
package.json # Local CJS override so Electron can require() compiled files
./src/components/TitleBar.tsx # Custom dark title bar with window controls
./electron-builder.yml # Packaging configuration
- Install dependencies
npm install- Start development (Vite + Electron):
npm run dev- Vite:
http://localhost:5173 - Electron: frameless window with custom TitleBar
Alternatively, run separately:
npm run dev:web
npm run dev:electron- Build the app
npm run build- Create an installer
npm run distOutputs are in dist/ (web assets) and installer artifacts under dist/ (e.g., .AppImage, .exe, .dmg depending on platform). On a local machine you only get the current OS installer. Use CI to build all OS installers at once.
- Place source icons in
public/icons/:favicon.ico(source; optional)256x256.png,512x512.png(Linux packaging reads PNGs with size in filename)
- The build step auto‑generates missing PNGs from
favicon.icowhen possible, or writes a safe placeholder so packaging never fails. - Electron window icon resolves to:
- Dev:
/icons/icon.pnginpublic/(if present) - Prod:
<resources>/icons/icon.png(copied viaextraResources)
- Dev:
- electron-builder icons:
- mac:
public/icons/icon.icns - win:
public/icons/icon.ico - linux:
public/icons(directory containingNNxNN.png)
- mac:
contextIsolation: true,nodeIntegration: false,sandbox: true- Minimal API exposed via preload; no direct Node access in renderer
- External links opened in system browser via
setWindowOpenHandler
If you see missing system libraries (e.g. libnss3.so):
sudo apt-get update
sudo apt-get install -y \
libnss3 libatk-bridge2.0-0t64 libgtk-3-0t64 libxss1 libasound2t64 libgbm1 \
libdrm2 libxdamage1 libxfixes3 libxrandr2 libxcomposite1 libxcursor1 \
libxi6 libxtst6 libcups2t64 libxkbcommon0 libcairo2 libpango-1.0-0 libpangocairo-1.0-0 \
libxcb-dri3-0- On Windows 11 with WSLg, GUI apps should display automatically
- On older setups, run an X server (e.g., VcXsrv) and export:
export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):0
export LIBGL_ALWAYS_INDIRECT=1-
Zoom shortcuts not working:
- Ensure app is restarted after changes
- Try both Ctrl + and Ctrl Shift = (keyboard layouts may differ)
- Numpad + is supported
-
Fullscreen gaps on Linux/WSL:
- Some compositors/X servers can leave margins with frameless windows. Use Maximize (double‑click title bar) or run under WSLg/native
-
"Cannot find module electron/dist/main.js":
- Use
npm run devso Electron TS is compiled (watch)
- Use
-
ESM vs CJS error in Electron main:
electron/package.jsonsets"type": "commonjs"for the Electron subtree
-
Windows shows old name/icon:
- Update
electron-builder.ymlproductNameand icons - Uninstall previous app before installing the new build to clear cached shortcuts
- Update
In preload.ts (exposed on window.desktop.window):
minimize(): voidtoggleMaximize(): Promise<boolean>toggleFullScreen(): Promise<boolean>close(): voidgetState(): Promise<{ isMaximized: boolean; isFullScreen: boolean; isFocused: boolean }>subscribeState((state) => void): () => void
Usage in React (see src/components/TitleBar.tsx).
- Configure
electron-builder.ymlwith your repo:publish: provider: github owner: YOUR_GITHUB_USERNAME repo: YOUR_REPO_NAME releaseType: draft
- Local publish (creates a draft release with current OS installer):
export GH_TOKEN=your_github_personal_access_token npm run release - CI auto‑publish (builds Windows/macOS/Linux):
- Push a tag like
v1.0.0to GitHub. The workflow.github/workflows/release.ymlbuilds on Windows/macOS/Linux and publishes assets to the tagged GitHub Release. - Installers appear on the release page for download. Download via:
- Releases:
https://github.com/<owner>/<repo>/releases - Latest:
https://github.com/<owner>/<repo>/releases/latest
- Releases:
- Push a tag like
- Run
npm run diston each OS to generate native installers indist/. - Share
.exe(Windows),.dmg(macOS),.AppImage(Linux) directly or upload to cloud storage.