Skip to content
Logo

Desktop (Electron)

The design decision

Desktop does not get a third renderer. Electron's renderer is Chromium, so the desktop target loads dist/index.htmlappDomJs and all — unchanged.

Web ↔ desktop renderer equivalence is therefore true by construction: it is the same file. There is no third code path to test, and no way for the desktop UI to drift from web.

What the shell owns is the window, and nothing else.

Emitting

let dc : DesktopChrome := { name := "hello-encapp", title := "Hello · encapp" }
IO.FS.createDirAll "dist/desktop"
IO.FS.writeFile "dist/desktop/main.cjs"     (AppGen.emitDesktopMain dc)
IO.FS.writeFile "dist/desktop/preload.cjs"  AppGen.emitDesktopPreload
IO.FS.writeFile "dist/desktop/package.json" (AppGen.emitDesktopPackageJson dc)
dist/desktop/main.cjs       the Electron main process
dist/desktop/preload.cjs    the preload bridge
dist/desktop/package.json   the package manifest

Chrome is data, again

structure DesktopChrome where
  name   : String
  title  : String
  width  : Nat := 390
  height : Nat := 844

The platform analog of DocChrome: the shell's presentation is app data and the emitter stays generic.

The default geometry is deliberate — 390×844 is the same viewport the web and native verification tiers use, so all three targets render the app at the same size out of the box and a screenshot diff across platforms is meaningful without configuration.

Security posture

The emitted main process is opinionated:

  • contextIsolation on;
  • no Node integration in the renderer;
  • a preload script beside the window, which is the only bridge.

The renderer runs the same untrusted-by-default web artifact it would in a browser tab. Nothing in the app's emitted code has access to the filesystem or to Node.

Path resolution

main.cjs resolves index.html in two places, in order:

  1. next to itself — the packaged layout copies the web artifact into the app directory;
  2. one level up — the dev layout, where dist/desktop/ sits beside dist/index.html.

So lake exe helloapp produces a tree you can run immediately, and packaging works without a different emit.

Running it

cd dist/desktop
npx electron .

Verification

node test/encapp-replay-electron.mjs dist /tmp/corpus.json
node test/electron-smoke.mjs

The electron tier launches a real Electron main and renderer and replays the same corpus through the same window.__enc hook. It is the slowest tier and the most faithful: it is the actual shipping shell, not a simulation of one.

test/_electron-bin.mjs resolves the Electron binary for the harness.

Packaging

Packaging and installers are ordinary Electron work — the emitted package.json gives a packager a valid starting point, and DesktopChrome.name sets the npm-style package name of the emitted app directory.

Recap

  • Desktop loads the web artifact (dist/index.html) unchanged; renderer equivalence by construction
  • DesktopChrome is data alongside main.cjs, preload.cjs, and package.json; emitter stays generic
  • Security: contextIsolation on, no Node.js integration in renderer, preload script as sole bridge
  • Path resolution tries packaged layout first, falls back to dev layout; no emit changes needed
  • The emitted shell is opinionated and minimal—only the window; untrusted web artifact runs as-is
  • Verification runs the actual Electron shell against the corpus; electron tier is slowest and most faithful
  • Packaging and installers are standard Electron work using the emitted manifest

Next steps