%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'monospace', "darkMode":true}}}%%
flowchart TD
PKG["src/nostalgia_index/"] --> SC(["scaffold_py.py"])
SC --> APP["electron/app/"]
APP --> CHK(["app_check()"])
CHK --> EXP(["export()"])
EXP --> DIST["electron/dist/electron-app/dist/"]
Desktop installer (plan)
This page is a plan for shipping the Nostalgia Index app as a desktop installer (.dmg, .exe, .AppImage) with the shinyelectron R package (version 0.2.1 or later). Nothing described here exists in the repository yet.
shinyelectron only understands an app directory (a folder with an app.py). The package is not an app directory, so the plan generates a thin launcher folder that imports the package, vendors a copy of the package next to it, and hands that folder to shinyelectron::export(). The package source stays as it is.
Entry point
The app already exposes a module level App object, so no source changes are needed.
# src/nostalgia_index/app/app.py
app = App(app_ui, server, static_assets=Path(__file__).parent / "www")The launcher imports it from nostalgia_index.app.app. run_app() is not used, because shinyelectron owns the port and the server process. The app name and version (nostalgia_index, 0.1.0) come from [project] in pyproject.toml.
Build machine
shinyelectron is an R package, so the build machine needs R (4.4 or later) and Node (22 or later, with npm 11.5 or later). End users need neither.
shinyelectron::sitrep_shinyelectron()
shinyelectron::install_nodejs() # only if Node is missingRuntime strategy
The plan uses the bundled strategy, which ships a portable Python with the installer (roughly 150 to 300 MB). The other strategies were ruled out for these reasons:
shinylive:httpxcannot open sockets inside Pyodide, anddiskcacherelies on SQLite file locking. The app could not reach the Wikimedia API.system: it requires users to have Python on theirPATH.container: there are no heavy system libraries to justify Docker.auto-download: it is a reasonable fallback if installer size becomes a problem, since the app needs internet access anyway.
The app is never fully offline. Every index computation calls the Wikimedia REST API, so the installer removes the Python requirement, not the network requirement.
Scaffold the launcher
Run the scaffold script from the package root. It is idempotent and should be re-run before every build to refresh the vendored copy.
python tools/scaffold_py.py \
--strategy=bundled \
--module=nostalgia_index.app.app \
--attr=app \
--platforms=mac,win,linuxIt creates the following files:
electron/
├── app/
│ ├── app.py # from nostalgia_index.app.app import app
│ ├── requirements.txt # [project].dependencies
│ ├── nostalgia_index/ # vendored copy (gitignored)
│ └── _shinyelectron.yml # name, version, strategy, dependencies
└── build.R # app_check() + export() into electron/dist
Two repository changes go with it. electron/dist/ and electron/app/nostalgia_index/ are added to .gitignore, and electron/ is excluded from the sdist.
[tool.hatch.build.targets.sdist]
exclude = ["electron"]Dependency checks
shinywidgetsandplotly: these are pure Python and install fromrequirements.txtinto the bundled runtime.custom.css: it is resolved withPath(__file__).parent, so it loads from the vendored copy regardless of the working directory.- Cache:
diskcachewrites to~/.cache/nostalgia_index(orNOSTALGIA_CACHE_DIR). This resolves on all three platforms, but the Windows path should be confirmed in a test build. - User-Agent:
USER_AGENTinclient.pystill holds placeholder contact details. These must be real before a public release (see Ethics and rate limits). - Python pin: set
dependencies.python.versionin_shinyelectron.ymlto a version that satisfiesrequires-python >= 3.10.
Validate and build
Rscript electron/build.Rbuild.R runs app_check("electron/app") and then export(). Only the contents of electron/dist/electron-app/dist/ get shipped. If the packaged app fails to start, launch it with SHINYELECTRON_DEBUG=1 to see runtime detection, dependency installs, and server startup.
Signing and CI
macOS installers only build on macOS, and bundled cannot cross-build, so each platform builds on its own CI runner.
- Copy
build-installers.ymlfrom the py-shinyelectron-pkg skill assets into.github/workflows/, and copyscaffold_py.pyintotools/(the workflow calls it there). - Signing uses
sign = TRUEwith theCSC_LINK,CSC_KEY_PASSWORD,APPLE_ID,APPLE_APP_SPECIFIC_PASSWORD, andAPPLE_TEAM_IDsecrets. Secrets go in the job levelenv:block, never in files. - Trigger the workflow on version tags so the installer version tracks
pyproject.toml.
Checklist
- [ ] Preflight the build machine (R, Node, npm)
- [ ] Add tools/scaffold_py.py and run it with --strategy=bundled
- [ ] Update .gitignore and the hatch sdist exclude
- [ ] Pin dependencies.python.version
- [ ] Replace the placeholder User-Agent contact details
- [ ] Run electron/build.R locally and test the installer
- [ ] Confirm the cache path on Windows
- [ ] Add the CI workflow and signing secrets
Version bumps
The installer version is pinned to the package version. Bump version in pyproject.toml and re-run the scaffold; never edit the version in _shinyelectron.yml by hand.