The desktop store put the catalog on ordinary computers, and then asked people to open a terminal — which on Windows is not even a workable ask, because the installer is `curl … | sh`. This is the window: a grid of cards, one click to install a title into the application menu, one to play it, one to remove it. The CLI stays the product. Every action runs `desktop_store.py --json`, so there is one catalog logic, one state file and one delete guard; the window never touches the filesystem itself. That is also why the engine grew `--json` first rather than this app growing a parser for prose. It doubles as the Windows install path: with no store on the machine, the app downloads the engine, the shared core and a config into the same folder the shell installer would use — Node's https, no curl. An engine older than 1.1.0 cannot be driven from a window, so the client checks the version and offers to refresh it instead of failing on the first call. Deliberate choices worth knowing: - No renderer framework and no build step. Plain HTML, CSS and JS, one runtime dependency. The whole UI is readable in one sitting. - `contextIsolation` on, `nodeIntegration` off, `sandbox` on, a CSP that permits only the app's own script and stylesheet plus images over HTTPS. The renderer can do exactly what preload.js exposes and nothing else. - English and Hungarian, following the system language. The CLIs and the docs stay English; this is the one end-user surface where that is not enough. - `ENGINES` is a list with one entry. The RetroArch store has the same command shape, so adding it is an entry, not a rewrite. Two ways to test it without a working installation in the way: `npm run smoke` drives the bridge with no window at all, and `npm run uitest` loads the window once and reports what rendered — the only way a renderer error would otherwise be noticed, since the main process log stays empty. Both accept a sandbox store through STORE_ROOT / SMOKE_HOME. Verified on macOS arm64, including the packaged .app: the store is found, ten titles list, a sync installs three, and the window renders them as installed with their Play and Remove buttons. Linux and Windows are unproven, as they are for the CLI itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
27 lines
1.2 KiB
JavaScript
27 lines
1.2 KiB
JavaScript
'use strict'
|
|
// The whole surface the renderer gets. No Node, no fs, no child_process — just
|
|
// these calls and two event streams.
|
|
|
|
const { contextBridge, ipcRenderer } = require('electron')
|
|
|
|
contextBridge.exposeInMainWorld('storeApi', {
|
|
state: () => ipcRenderer.invoke('app:state'),
|
|
setLocale: (locale) => ipcRenderer.invoke('app:setLocale', locale),
|
|
|
|
list: () => ipcRenderer.invoke('store:list'),
|
|
paths: () => ipcRenderer.invoke('store:paths'),
|
|
sync: (names) => ipcRenderer.invoke('store:sync', names),
|
|
remove: (name) => ipcRenderer.invoke('store:remove', name),
|
|
bootstrap: () => ipcRenderer.invoke('store:bootstrap'),
|
|
launch: (game) => ipcRenderer.invoke('store:launch', game),
|
|
|
|
openFolder: (dir) => ipcRenderer.invoke('app:openFolder', dir),
|
|
openExternal: (url) => ipcRenderer.invoke('app:openExternal', url),
|
|
|
|
// Streams from the running CLI: `log` is a line a person can read, `event` is
|
|
// one of the store's JSON progress events.
|
|
onLog: (fn) => ipcRenderer.on('store:log', (_e, line) => fn(line)),
|
|
onEvent: (fn) => ipcRenderer.on('store:event', (_e, event) => fn(event)),
|
|
onBusy: (fn) => ipcRenderer.on('store:busy', (_e, value) => fn(value))
|
|
})
|