A House Divided Desktop A House Divided DesktopDocumentation
Play
Engineering/Contributing

Desktop client architecture

Last updated 2026-07-10

High level#

Key modules#

Area Files
App menu, Navigate, View menu.js, nav.js, countries.js, urls.js
Client manifest site-api.js (GET /api/client-nav, /api/countries), nav-manifest.js (normalization)
Corporation enrichment corporation-enrich.js (pathId, ceoId, strip/merge)
Game menu quick links game-panel-links.js, game-panel-config.html, game-panel-config-window.js
PiP / mini mode pip.js, pip.html, pip-view-poller.js, dashboard.js
Tray & notifications tray.js, notifications.js
Pop-out presets windows.js
Global shortcuts shortcuts.js (defaults + customShortcuts preference)
Theme / cache / queue cache.js, action-queue.js, sse.js

Client-nav pipeline#

  1. After each full load (did-finish-load), on first SSE connect, and on a focus-aware interval (30s focused / 60s unfocused), the main process calls GET /api/client-nav with the same cookies as the game window.
  2. The JSON response is normalized in nav-manifest.js (e.g. character_countryIdcharacterCountryId, derived hasCharacter when the API omits the flag but sends character-related fields).
  3. Optionally enriched from GET /api/character/me for corporation / CEO fields (enrichClientNavManifest in main.js).
  4. Effects: IPC to renderer (client-nav, nav-data-updated, auth-state, unread counts), MenuManager.setNavConfig, window preset updates, and game-state hydration for tray/PiP.

If the first fetch returns null (timing, parse error), pullClientNav({ retryOnNull: true }) retries with backoff so the Navigate menu is not stuck until the next poll. Both focus and show events on the main window also trigger a pullClientNav so the menu refreshes on window restore as well as focus gain. Navigating to /corporation or /stockmarket paths triggers an automatic re-sync.

Preload surface (window.ahdClient)#

The game page may invoke only channels listed in preload.js (INVOKE_CHANNELS) and listen on RECEIVE_CHANNELS. Common uses:

Game panel configuration window#

A separate small BrowserWindow loads game-panel-config.html with game-panel-config-preload.js, which exposes window.gamePanelConfig (not ahdClient). It talks to main via IPC: game menu entries, PiP status bar stats, and keyboard shortcut overrides (get-custom-shortcuts / save-shortcuts).

Dynamic countries#

After each SSE connect (and on startup from cache), the main process calls GET /api/countries via site-api.fetchCountries(). The response is stored in cache.js (countries key) and set on countries.js via setCountriesCache(). All subsequent getCountryConfig() calls prefer the server list; hardcoded defaults in COUNTRIES serve only as offline fallback. If the list changes (different length or ids), the menu is rebuilt immediately.

Corporation enrichment (corporation-enrich.js)#

enrichClientNavManifest in main.js delegates to mergeCharacterMeIntoManifest() after fetching /api/character/me. The module:

All corporation URL construction (menu.js, game-panel-links.js, pip.html) wraps the id segment in encodeURIComponent for slug safety.

SSE fallback polling#

When SSE disconnects (common with Vercel multi-instance), main.js starts a 30-second setInterval that calls fetchClientNav() and pipes the result through handleClientNav(). The timer is cleared on SSE reconnect and on app cleanup.

Version header#

All authenticated net.request() calls (site-api.js) send X-AHD-Client-Version: <package.json version> so the server can detect outdated clients.

Builds#

electron-builder targets: Windows NSIS (.exe), macOS DMG, Linux AppImage. CI builds are unsigned (CSC_IDENTITY_AUTO_DISCOVERY=false); see the main README for macOS Gatekeeper notes.

Ask