Zenthyr Architecture
Architecture
Zenthyr runs your backend and UI in the same JVM process, with the UI rendered by JCEF (Chromium). During development, the UI is served by Vite, and the backend communicates with the UI via JCEF’s message router (cefQuery).
Overview
At a high level, the moving parts are:
-
Clojure backend (JVM)
- Owns app lifecycle and native integration
- Starts/stops the frontend dev server during development
-
JCEF (Chromium) window
- Renders your web UI inside the desktop app window
- Hosts the IPC bridge used by the frontend
-
Vite frontend (React/Vue/Svelte/Angular)
- Served from
http://localhost:<port>during development - Hot reload updates the UI while the JVM stays running
- Served from
Communication Flow
[Frontend UI] -- window.cefQuery(JSON) --> [Clojure handler][Clojure handler] -- JSON response --> [Frontend UI]
[Vite dev server] -- http://localhost:<port> --> [JCEF window] -- loads --> [Frontend UI]IPC (Frontend ↔ Backend)
Zenthyr injects a small bridge into the page, exposing:
window.zenthyr.invoke(message)→ returns a Promise resolving to a JSON responsewindow.zenthyr.emit(message)→ fire-and-forget style message
On the backend, you provide a :handler function to zenthyr/start-app! which receives the parsed JSON message and returns a Clojure map that will be encoded back to JSON.
Window/Process Lifecycle (macOS)
Zenthyr aims to behave like a native macOS app:
- Window close button hides the window but keeps the process alive (dock icon stays)
- Dock icon click reopens the window
- Quit (Cmd+Q or Dock menu Quit) exits the JVM and shuts down child processes (including Vite)
Next Steps
- Build the Frontend
- Implement the Backend handler