* docs: move superpowers/research artifacts to isolated _tasks repo + docs tree cleanup
- Move docs/superpowers/{plans,specs} and docs/research/* into the gitignored,
separately-versioned _tasks/ repo; untrack the two tracked research design docs.
- Add CLAUDE.md "Planning & Research Artifacts" section overriding the superpowers
default save paths (docs/... -> _tasks/...); align REPOSITORY_MAP and
DOCUMENTATION_OVERHAUL_PLAN with the new convention.
- Drop 4 now-obsolete /api/discovery/* entries from check-docs-symbols allowlist
(stale-enforcement) and refresh code/spec path comments to _tasks/...
- Sweeps in concurrent docs-tree restructuring (root-level provider/guide docs,
compression spec cleanup, .mcp.json.example removal).
* docs: reorganize docs/ tree + fix stale facts across ~26 docs
Phase A — reorganization:
- Move 7 orphan root docs into subfolders (providers/ created; TIERS+USAGE_QUOTA→guides/;
plugins+PLUGIN_SDK→frameworks/); delete 8 obsolete/redundant docs (SUBMIT_PR superseded
by CONTRIBUTING; DOCUMENTATION_OVERHAUL_PLAN; INCIDENT_RESPONSE/PERF_BUDGETS/THREAT_MODEL;
3 ops snapshots). Rebuild README index (was missing ~40 files) + per-folder meta.json nav.
- Clean 14 dangling doc-path references in bin/ ops scripts, scripts/, workflow, tests;
fix the dockerignore-docs-coverage required-docs path (PROVIDERS→providers/CLAUDE_WEB).
Phase B — content accuracy (verified against code, not the audit summary):
- Functional: ENVIRONMENT flag defaults (INPUT_SANITIZER/MCP_ENFORCE_SCOPES=true,
COMPRESS_DESCRIPTIONS=false, dynamic heap); MCP-SERVER notion tool names (omniroute_*→
notion_*) + counts 87→94; coverage gate 75/70→60/60/60/60 (RELEASE_CHECKLIST, COVERAGE_PLAN,
ERROR_SANITIZATION, CONTRIBUTING); pre-push hook description; regenerate PROVIDER_REFERENCE (237).
- Count drift: providers 237, executors 70, migrations 106, db modules 94, oauth 19,
strategies 17, MCP 94, flags 38, TS 6.0, open-sse ~900/services 294 across architecture/
frameworks/ops docs; AUTO-COMBO 9→12 factors w/ correct DEFAULT_WEIGHTS; REASONING +2
patterns; STEALTH UA defaults; AGENT_PROTOCOLS +cursor-cloud/list-capabilities;
LANGUAGE_PACKS +id pack.
- Kept Node 20 (runtime guard accepts 20.20.2+; only engines is stricter) and MCP scopes=13
(mcpScopes.ts) — both were correct in the docs; corrected only the attribution.
* docs: finish content refresh — compression engines, CLI_TOKEN merge, metadata sweep
- Compression: document the additional built-in engines (CCR, headroom, ionizer,
session-dedup) in COMPRESSION_ENGINES; clarify LLMLingua-2 is the ultra-mode SLM
backend + cross-ref the extra engines in EXTENDING_COMPRESSION; add the id
(Indonesian) language pack to LANGUAGE_PACKS.
- AUTO-COMBO: replace the orphan 'How tiers fit' weight table (stale weights) with a
pointer to the canonical 12-factor DEFAULT_WEIGHTS table.
- Security: merge CLI_TOKEN_AUTH.md (legacy 32-char SHA-256 format) into CLI_TOKEN.md
as a 'Legacy format — still accepted' section (server accepts both HMAC + legacy),
delete CLI_TOKEN_AUTH.md, drop it from the index + security nav.
- Metadata: bump stale frontmatter (version/lastUpdated) to 3.8.40/2026-06-28 across the
doc set audited this pass, and normalize the in-body 'Last updated' header lines to match.
* fix(runtime): drop Node 20 from supported range + align all docs/diagrams/counts
- Node minimum is now 22 (aligned with package.json engines). SUPPORTED_NODE_RANGE in
src/shared/utils/nodeRuntimeSupport.ts (and the bin/ mirror) drops the 20.x line →
'>=22.22.2 <23 || >=24.0.0 <27'; getNodeRuntimeSupport now rejects Node 20 as
unsupported-major. Test updated (TDD): node-runtime-support.test.ts asserts Node 20
rejected. Docs aligned (TROUBLESHOOTING ×2, TERMUX, RELEASE_CHECKLIST, CODEBASE,
CLI-TOOLS, README, llm.txt + 42 i18n llm.txt mirrors, skills/cli-serve).
- Diagrams regenerated: mcp-tools-87 -> mcp-tools-94 (34 base + pool 6 = 94) and
auto-combo-9factor -> auto-combo-12factor (correct DEFAULT_WEIGHTS); SVGs re-rendered
via mermaid-cli; doc refs + diagrams/README updated; fixed a pre-existing broken
resilience-3layers image path.
- CLAUDE.md + AGENTS.md aligned to real counts (237 providers, 94 MCP tools / 34 base,
106 migrations, 94 db modules, 12-factor auto-combo, 17 strategies); README provider
count 231 -> 237; executor count corrected to 68 (provider executors, excl base/index)
and OAuth to 18 across architecture docs. check:docs-all now passes (0 strict drift,
0 broken links); removed dead .mcp.json.example doc link.
* fix(services): update installer Node hint to >=22.22.2 (aligned with dropped Node 20)
* docs: realign counts to current release tip after rebase
The release tip advanced while this work was in flight (Gemini CLI provider/executor
removed by #5246, plus other PRs). Re-counted against the current code and updated:
providers 237->236, executors 68->67, OAuth modules 18->17, open-sse services 294->298;
regenerated PROVIDER_REFERENCE.md (236). check:docs-all passes (0 strict drift).
* docs(changelog) + i18n: record Node 20 drop + fix nodeIncompatibleHint
- CHANGELOG: add [3.8.40] entries for the Node 20.x removal (runtime) and the docs
reorganization/accuracy audit.
- i18n: nodeIncompatibleHint across all 42 locales no longer lists Node 20.x as
supported (ASCII + CJK full-width variants), aligned with the dropped Node 20.
* fix(docs): repair CI breakages from the doc moves
- test: cli-plugin-system asserted docs/dev/plugins.md exists; the file moved to
docs/frameworks/PLUGINS.md — point the test at the new path (Unit fast-path 2/2 fix).
- frontmatter: PLUGINS.md and PLUGIN_SDK.md moved into the fumadocs-indexed
docs/frameworks/ which requires a 'title' frontmatter; the missing frontmatter
failed the Next.js MDX build (dast-smoke 'invalid frontmatter'). Added frontmatter
to both, plus the providers/ docs (consistency; that folder is not indexed).
13 KiB
title, version, lastUpdated
| title | version | lastUpdated |
|---|---|---|
| Electron Desktop Guide | 3.8.40 | 2026-06-28 |
Electron Desktop Guide
Source of truth:
electron/workspace Last updated: 2026-06-28 — v3.8.40
OmniRoute ships a cross-platform desktop app (Windows / macOS / Linux) built on
Electron 41 + electron-builder 26.10. The desktop app spawns the Next.js
standalone server as a child process, points a BrowserWindow at it, and adds a
system tray, auto-updater, IPC bridge, and zero-config secret bootstrap.
Architecture
┌──────────────────────────────────────────────┐
│ Electron main process (electron/main.js) │
│ ├─ Single-instance lock │
│ ├─ Child process: Next.js standalone server │
│ │ (spawned with Electron's Node runtime) │
│ ├─ BrowserWindow → http://localhost:PORT │
│ ├─ System tray + context menu │
│ ├─ Auto-update via electron-updater │
│ ├─ Content Security Policy (session headers) │
│ └─ Secret bootstrap (JWT / API_KEY_SECRET) │
└──────────────────────────────────────────────┘
↕ IPC bridge (electron/preload.js)
┌──────────────────────────────────────────────┐
│ Renderer (Next.js dashboard) │
│ window.electronAPI.* (contextIsolation) │
└──────────────────────────────────────────────┘
Versions
Confirmed from electron/package.json:
| Package | Version |
|---|---|
electron |
^41.5.1 |
electron-builder |
^26.10.0 |
electron-updater |
^6.8.5 |
better-sqlite3 |
^12.9.0 |
| App version | 3.8.0 |
| App id | online.omniroute.desktop |
| Product name | OmniRoute |
Scripts (root package.json)
| Script | Purpose |
|---|---|
npm run electron:dev |
Starts npm run dev + waits for localhost:20128 + launches Electron |
npm run electron:build |
Builds Next.js then runs electron-builder for the current OS |
npm run electron:build:win |
Builds Windows NSIS installer + portable (x64) |
npm run electron:build:mac |
Builds macOS DMG (Intel + Apple Silicon) |
npm run electron:build:linux |
Builds Linux AppImage + DEB (x64 + arm64) |
npm run electron:smoke:packaged |
Launches packaged binary and probes /login for HTTP 200, then shuts down |
The electron/ workspace also exposes:
npm run prepare:bundle— runsscripts/build/prepare-electron-standalone.mjsnpm run build:mac-x64/build:mac-arm64— single-arch macOS buildsnpm run pack— directory-only build for local testing (no installer)
Directory Layout
electron/
├── package.json # Electron deps + electron-builder config
├── main.js # Main process (24 KB — see annotations below)
├── preload.js # contextBridge IPC bridge
├── types.d.ts # AppInfo / ServerStatus / ElectronAPI types
├── README.md # In-workspace notes
├── assets/ # icon.png, icon.ico, icon.icns, tray-icon.png
└── dist-electron/ # electron-builder output (gitignored)
scripts/
├── build/
│ └── prepare-electron-standalone.mjs # Stages .next/electron-standalone bundle
└── dev/
└── smoke-electron-packaged.mjs # Post-build smoke test
Both main.js and preload.js are CommonJS .js files, not TypeScript. The
renderer-side typings live in electron/types.d.ts.
IPC Bridge (preload.js)
The preload exposes a whitelisted API on window.electronAPI using contextBridge
with contextIsolation: true and nodeIntegration: false.
const VALID_CHANNELS = {
invoke: [
"get-app-info",
"open-external",
"get-data-dir",
"restart-server",
"check-for-updates",
"download-update",
"install-update",
"get-app-version",
],
send: ["window-minimize", "window-maximize", "window-close"],
receive: ["server-status", "port-changed", "update-status"],
};
Exposed methods:
| Renderer call | Type |
|---|---|
getAppInfo() → { name, version, platform, isDev, port } |
invoke |
openExternal(url) |
invoke |
getDataDir() |
invoke |
restartServer() |
invoke |
getAppVersion() |
invoke |
checkForUpdates() / downloadUpdate() / installUpdate() |
invoke |
minimizeWindow() / maximizeWindow() / closeWindow() |
send |
onServerStatus(cb) / onPortChanged(cb) / onUpdateStatus(cb) |
receive (returns disposer) |
The receive helpers return a disposer function rather than relying on
removeAllListeners — this prevents listener accumulation when React components
remount.
Server Lifecycle
main.js spawns the Next.js standalone bundle directly with the Electron Node
runtime to avoid native-module ABI mismatch with system Node:
spawn(process.execPath, [serverScript], {
cwd: NEXT_SERVER_PATH,
env: { ...serverEnv, PORT, NODE_ENV: "production", ELECTRON_RUN_AS_NODE: "1", NODE_PATH },
stdio: "pipe",
});
Highlights:
waitForServer()polls the URL up to 30 s before showing the window (no blank screen on cold start).stdio: "pipe"captures stdout/stderr; ready phrases (Ready/listening) emitserver-status: runningover IPC.before-quitwaits up to 5 s for graceful SIGTERM (WAL checkpoint) then sends SIGKILL.- Port switcher in the tray (
20128,3000,8080) stops and restarts the server, then reloads the BrowserWindow.
Zero-config Secret Bootstrap
On first launch, the main process auto-generates and persists missing secrets:
| Secret | Source |
|---|---|
JWT_SECRET |
crypto.randomBytes(64).toString("hex") |
STORAGE_ENCRYPTION_KEY |
crypto.randomBytes(32).toString("hex") (refuses if encrypted creds already exist) |
API_KEY_SECRET |
crypto.randomBytes(32).toString("hex") |
Persisted to <DATA_DIR>/server.env. DATA_DIR resolves to:
- Windows:
%APPDATA%\omniroute - Linux:
$XDG_CONFIG_HOME/omnirouteor~/.omniroute - macOS:
~/.omniroute
Window & Tray
BrowserWindow: 1400×900 (min 1024×700),backgroundColor: "#0a0a0a".- macOS:
titleBarStyle: "hiddenInset", traffic-light at{ x: 16, y: 16 }. - Windows/Linux: native title bar.
- Close button minimizes to tray; the tray menu has Open OmniRoute, Open Dashboard (external browser), Server Port submenu, Check for Updates, Quit.
Content Security Policy
Set via session.defaultSession.webRequest.onHeadersReceived. Notable directives:
frame-ancestors 'none',object-src 'none',child-src 'none'connect-src 'self' http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* https://*.omniroute.online https://*.omniroute.dev- Dev mode adds
'unsafe-eval'toscript-srconly
Auto-update
Uses electron-updater with the GitHub provider (diegosouzapw/OmniRoute).
autoDownload = false,autoInstallOnAppQuit = true- Events forwarded to renderer via
update-statusIPC:checking,available,not-available,downloading(withpercent),downloaded,error installUpdate()kills the server then callsautoUpdater.quitAndInstall()- Skipped in dev mode (
!app.isPackaged)
Build Pipeline
npm run build→ Next.js standalone in.next/standalone.prepare-electron-standalone.mjs→ re-stages into.next/electron-standaloneand rewrites absolute paths insideserver.js+required-server-files.jsonso the bundle is relocatable.electron-builderpackagesmain.js,preload.js,node_modules, andextraResources: { ../.next/electron-standalone → app }.
Build targets
| OS | Targets |
|---|---|
| Windows | NSIS installer + portable (x64) |
| macOS | DMG (Intel + arm64, drag-to-Applications) |
| Linux | AppImage + DEB (x64 + arm64) |
NSIS settings: oneClick: false, lets the user choose the install directory, creates Desktop and Start-Menu shortcuts.
Smoke Testing Packaged Build
npm run electron:smoke:packaged
scripts/dev/smoke-electron-packaged.mjs:
- Auto-discovers the packaged binary in
electron/dist-electron/for the current platform. - Launches with isolated
HOME/APPDATA/XDG_*directories so it doesn't touch developer data. - Polls
http://127.0.0.1:20128/loginfor HTTP 200 within 45 s. - Watches stderr/stdout for fatal patterns (
Cannot find module,MODULE_NOT_FOUND,ERR_DLOPEN_FAILED,Failed to start server, etc.). - Waits 2 s of stable runtime after readiness, then issues SIGTERM and waits for the port to free.
- In CI, automatically passes
--no-sandbox --disable-gpu(and--disable-dev-shm-usageon Linux).
Env overrides: ELECTRON_SMOKE_APP_EXECUTABLE, ELECTRON_SMOKE_URL, ELECTRON_SMOKE_TIMEOUT_MS, ELECTRON_SMOKE_SETTLE_MS, ELECTRON_SMOKE_DATA_DIR, ELECTRON_SMOKE_KEEP_DATA, ELECTRON_SMOKE_STREAM_LOGS.
Code Signing
electron/package.json does not wire signing credentials directly. Pass them via env vars to electron-builder:
macOS
export APPLE_ID=<email>
export APPLE_APP_SPECIFIC_PASSWORD=<password>
export APPLE_TEAM_ID=<id>
export CSC_LINK=path/to/cert.p12
export CSC_KEY_PASSWORD=<cert-password>
npm run electron:build:mac
Windows
export CSC_LINK=path/to/cert.pfx
export CSC_KEY_PASSWORD=<cert-password>
npm run electron:build:win
Linux
AppImage signing is optional — set LINUX_GPG_KEY if signing.
Distribution
Artifacts land in electron/dist-electron/:
OmniRoute Setup X.Y.Z.exe,OmniRoute-X.Y.Z-portable.exe(Windows)OmniRoute-X.Y.Z-mac.dmg,OmniRoute-X.Y.Z-arm64-mac.dmg(macOS)OmniRoute-X.Y.Z.AppImage,omniroute-desktop_X.Y.Z_amd64.deb(Linux)
Releases are published to GitHub Releases (diegosouzapw/OmniRoute), which is also where electron-updater checks for new versions.
Troubleshooting
| Symptom | Fix |
|---|---|
Cannot find module 'better-sqlite3' after Electron major bump |
cd electron && npm rebuild |
ERR_DLOPEN_FAILED for native module |
Re-run prepare:bundle and verify ABI matches Electron's Node |
| Window appears blank on Linux | Confirm Next.js server actually bound to PORT (check [Server] logs) |
| macOS notarization stalls | Ensure APPLE_* vars are exported, not just in .env |
| Windows SmartScreen warning | Sign with EV cert, or users right-click → "Run anyway" |
| Smoke test fails with port-in-use | Stop any local dev server on 20128 before running electron:smoke:packaged |
See Also
- SETUP_GUIDE.md
- RELEASE_CHECKLIST.md
- Source:
electron/main.js,electron/preload.js,electron/package.json - Helpers:
scripts/build/prepare-electron-standalone.mjs,scripts/dev/smoke-electron-packaged.mjs