The macOS app and the Windows bundle shipped every module in the repo, switched on by default, and a shell update silently added whatever new module had landed on main — that is how Band Lineup appeared on AB. A bundled module was also only as fresh as the last shell update. The decision: the shell ships alone, and every module arrives through the admin page's catalog.
What
A packaged install carries no modules. "Packaged" is any install that is not a git checkout — the same test the updater already uses to choose the archive over git pull. For those, isKeptOnUpdate() refuses modules/ from the shell archive, so an update can never bring a module along. A git checkout keeps running the modules in its tree, which is what development wants.
Existing installs move over by themselves. At the first start, unbundleModules() copies each bundled module that is switched on into the data directory as an installed copy (fs.cpSync, so a compiled helper keeps its executable bit), drops one the operator had uninstalled, then removes the app folder's modules/. Settings are keyed by module id, so nothing changes for the operator; from then on each module updates on its own from the catalog. A failed move leaves the folder in place and is retried at the next start.
Install several at once.POST /api/admin/modules/install {ids} runs the existing per-module install for each id and answers one result per id; the admin page puts a checkbox on every compatible catalog entry and an Install selected button (shown when two or more are on offer).
Empty states. The dashboard says "No modules yet — open Admin and install some under Available modules" when the registry is empty, and returns to "No tiles yet" once something is installed; the admin page says "Nothing installed yet — pick from Available modules below."
Builds.launcher/macos/build.sh stops copying modules/ into the payload and no longer compiles or signs Swift helpers — those ship prebuilt inside a module, per the guide's new rule (PR #35). The Windows build script lives in PR #24 and gets the same one-line change when that lands.
tools/unbundled.test.js (new): a packaged copy of the shell in a temp folder against a mock forge — the bundled module moves into the data directory with its helper still executable, the uninstalled one is dropped, the app folder's modules/ is gone, the module runs as an installed copy; a shell update from an archive carrying modules/ exits 75 for its supervisor and leaves no modules/ behind while the shell itself is updated; the new modules appear as catalog offers; installing several at once lands them in the data directory and mounts them, with one bad id reported and the rest installed.
tools/layout-roundtrip.test.js still passes.
In the browser, a scratch packaged 1.10.0 with an empty data directory against the real Gitea catalog: the dashboard shows the no-modules state, admin lists all eleven repo modules with checkboxes, ticking Clock and Service Times and pressing Install selected installed both from the repo and mounted them, and the dashboard flipped to "No tiles yet".
Rollout
Build the 1.10.0 images, update AB and NA from their admin pages, confirm their modules survived the move, then confirm LTC on both and SPL on AB still capture from the data directory.
## Why
The macOS app and the Windows bundle shipped every module in the repo, switched on by default, and a shell update silently added whatever new module had landed on main — that is how Band Lineup appeared on AB. A bundled module was also only as fresh as the last shell update. The decision: the shell ships alone, and every module arrives through the admin page's catalog.
## What
- **A packaged install carries no modules.** "Packaged" is any install that is not a git checkout — the same test the updater already uses to choose the archive over `git pull`. For those, `isKeptOnUpdate()` refuses `modules/` from the shell archive, so an update can never bring a module along. A git checkout keeps running the modules in its tree, which is what development wants.
- **Existing installs move over by themselves.** At the first start, `unbundleModules()` copies each bundled module that is switched on into the data directory as an installed copy (`fs.cpSync`, so a compiled helper keeps its executable bit), drops one the operator had uninstalled, then removes the app folder's `modules/`. Settings are keyed by module id, so nothing changes for the operator; from then on each module updates on its own from the catalog. A failed move leaves the folder in place and is retried at the next start.
- **Install several at once.** `POST /api/admin/modules/install {ids}` runs the existing per-module install for each id and answers one result per id; the admin page puts a checkbox on every compatible catalog entry and an **Install selected** button (shown when two or more are on offer).
- **Empty states.** The dashboard says "No modules yet — open Admin and install some under Available modules" when the registry is empty, and returns to "No tiles yet" once something is installed; the admin page says "Nothing installed yet — pick from Available modules below."
- **Builds.** `launcher/macos/build.sh` stops copying `modules/` into the payload and no longer compiles or signs Swift helpers — those ship prebuilt inside a module, per the guide's new rule (PR #35). The Windows build script lives in PR #24 and gets the same one-line change when that lands.
- **Docs.** Module guide installation paragraph, README (Run it, Modules), launcher README, CLAUDE.md. Shell **1.10.0**.
## Verified
- `tools/unbundled.test.js` (new): a packaged copy of the shell in a temp folder against a mock forge — the bundled module moves into the data directory with its helper still executable, the uninstalled one is dropped, the app folder's `modules/` is gone, the module runs as an installed copy; a shell update from an archive carrying `modules/` exits 75 for its supervisor and leaves no `modules/` behind while the shell itself is updated; the new modules appear as catalog offers; installing several at once lands them in the data directory and mounts them, with one bad id reported and the rest installed.
- `tools/layout-roundtrip.test.js` still passes.
- In the browser, a scratch packaged 1.10.0 with an empty data directory against the real Gitea catalog: the dashboard shows the no-modules state, admin lists all eleven repo modules with checkboxes, ticking Clock and Service Times and pressing Install selected installed both from the repo and mounted them, and the dashboard flipped to "No tiles yet".
## Rollout
Build the 1.10.0 images, update AB and NA from their admin pages, confirm their modules survived the move, then confirm LTC on both and SPL on AB still capture from the data directory.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The macOS app and the Windows bundle shipped every module in the repo, on
by default, and a shell update silently added whatever new module had
landed on main — Band Lineup appeared on AB that way. A module was also
only as fresh as the last shell update. Now a packaged install (no .git —
the same test the updater uses to choose the archive over git pull)
carries no modules at all: every one arrives from the admin page's catalog
into the data directory, and the shell's own update never writes modules/.
A git checkout keeps running the modules in its tree.
Existing installs move over by themselves: at the first start, the bundled
modules that are switched on are copied into the data directory as
installed copies (a compiled helper keeps its mode), an uninstalled one is
dropped, and the app folder's modules/ is removed. Settings are keyed by
module id, so nothing changes for the operator.
Around it: the dashboard's empty state says "No modules yet — open Admin"
when the registry is empty; the admin page says "Nothing installed yet",
puts a checkbox on every compatible catalog entry and an Install selected
button (POST /api/admin/modules/install {ids}) that fetches them in one go.
The Mac build stops copying modules/ and no longer compiles or signs Swift
helpers — those ship prebuilt inside a module, per the guide.
tools/unbundled.test.js drives a packaged copy of the shell through the
move, an archive update that must not bring modules back, and a multi-install.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
michael
merged commit 407eaecc23 into main2026-09-30 09:42:27 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Why
The macOS app and the Windows bundle shipped every module in the repo, switched on by default, and a shell update silently added whatever new module had landed on main — that is how Band Lineup appeared on AB. A bundled module was also only as fresh as the last shell update. The decision: the shell ships alone, and every module arrives through the admin page's catalog.
What
git pull. For those,isKeptOnUpdate()refusesmodules/from the shell archive, so an update can never bring a module along. A git checkout keeps running the modules in its tree, which is what development wants.unbundleModules()copies each bundled module that is switched on into the data directory as an installed copy (fs.cpSync, so a compiled helper keeps its executable bit), drops one the operator had uninstalled, then removes the app folder'smodules/. Settings are keyed by module id, so nothing changes for the operator; from then on each module updates on its own from the catalog. A failed move leaves the folder in place and is retried at the next start.POST /api/admin/modules/install {ids}runs the existing per-module install for each id and answers one result per id; the admin page puts a checkbox on every compatible catalog entry and an Install selected button (shown when two or more are on offer).launcher/macos/build.shstops copyingmodules/into the payload and no longer compiles or signs Swift helpers — those ship prebuilt inside a module, per the guide's new rule (PR #35). The Windows build script lives in PR #24 and gets the same one-line change when that lands.Verified
tools/unbundled.test.js(new): a packaged copy of the shell in a temp folder against a mock forge — the bundled module moves into the data directory with its helper still executable, the uninstalled one is dropped, the app folder'smodules/is gone, the module runs as an installed copy; a shell update from an archive carryingmodules/exits 75 for its supervisor and leaves nomodules/behind while the shell itself is updated; the new modules appear as catalog offers; installing several at once lands them in the data directory and mounts them, with one bad id reported and the rest installed.tools/layout-roundtrip.test.jsstill passes.Rollout
Build the 1.10.0 images, update AB and NA from their admin pages, confirm their modules survived the move, then confirm LTC on both and SPL on AB still capture from the data directory.
🤖 Generated with Claude Code
The macOS app and the Windows bundle shipped every module in the repo, on by default, and a shell update silently added whatever new module had landed on main — Band Lineup appeared on AB that way. A module was also only as fresh as the last shell update. Now a packaged install (no .git — the same test the updater uses to choose the archive over git pull) carries no modules at all: every one arrives from the admin page's catalog into the data directory, and the shell's own update never writes modules/. A git checkout keeps running the modules in its tree. Existing installs move over by themselves: at the first start, the bundled modules that are switched on are copied into the data directory as installed copies (a compiled helper keeps its mode), an uninstalled one is dropped, and the app folder's modules/ is removed. Settings are keyed by module id, so nothing changes for the operator. Around it: the dashboard's empty state says "No modules yet — open Admin" when the registry is empty; the admin page says "Nothing installed yet", puts a checkbox on every compatible catalog entry and an Install selected button (POST /api/admin/modules/install {ids}) that fetches them in one go. The Mac build stops copying modules/ and no longer compiles or signs Swift helpers — those ship prebuilt inside a module, per the guide. tools/unbundled.test.js drives a packaged copy of the shell through the move, an archive update that must not bring modules back, and a multi-install. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>