Desktop (Electron)
The design decision
Desktop does not get a third renderer. Electron's renderer is Chromium, so the desktop
target loads dist/index.html — appDomJs 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 := 844The 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:
contextIsolationon;- 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:
- next to itself — the packaged layout copies the web artifact into the app directory;
- one level up — the dev layout, where
dist/desktop/sits besidedist/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.mjsThe 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 DesktopChromeis data alongsidemain.cjs,preload.cjs, andpackage.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;
electrontier is slowest and most faithful - Packaging and installers are standard Electron work using the emitted manifest
Next steps
- Web — Self-contained SPA with no build step
- Native (React Native) — One core, two renderers
- Testing and coverage gates — How the Electron verification tier works