From 07e4c7841768a9ee999c8ebae36ca2ea27f454bc Mon Sep 17 00:00:00 2001 From: Waters Church Date: Tue, 29 Sep 2026 16:34:34 -0400 Subject: [PATCH] Module rules: a native helper ships prebuilt, for Apple silicon and Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The machine running ProdDash is a packaged app or the Windows bundle plus modules from the admin page's catalog. It has no compiler, and the catalog delivers exactly the folder that is in the repo — so a module's native program (audio capture, a decoder) ships already built, inside the module folder, one file per platform: bin/-macos-arm64 and bin/-win-x64.exe are required, Intel macOS optional, source and the script that rebuilds every binary under native/. The guide's rule 3 now says so, "The shape of a module" shows the layout, and a new "Native helpers" section gives the contract: pick the binary by platform at runtime, report the feature off when there is none — never build, never search the machine, never ask the operator to install anything — one command-line contract in any language per platform, bump the version when a binary changes, keep them lean, sign the macOS one. CLAUDE.md carries the one-line version. Rules only: no module changes here. Co-Authored-By: Claude Fable 5.1 --- CLAUDE.md | 7 ++++- docs/MODULE-GUIDE.md | 69 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 10308d3..ab20021 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,12 @@ title-bar controls). The rules that matter most: ship inside the module folder** (zero deps is the norm). **Everything the module does runs inside its server entry** — no helper scripts, companion programs or daemons the operator starts by hand; a listener or decoder is - started in `init()` and stopped in `stop()`. Declare `"proddash": ">=x.y.z"` + started in `init()` and stopped in `stop()`. **A native helper ships + prebuilt inside the module folder for Apple silicon macOS and 64-bit + Windows** (`bin/-macos-arm64`, `bin/-win-x64.exe`, source and + build script under `native/`); nothing is ever compiled on the machine + running ProdDash, and a missing binary means the feature reports itself + off, not a broken module. Declare `"proddash": ">=x.y.z"` and bump the module's `version` on every change. - Browsers never call upstream services directly; the module's server routes proxy everything (no CORS, secrets stay server-side). diff --git a/docs/MODULE-GUIDE.md b/docs/MODULE-GUIDE.md index 4660e0e..7de6e4f 100644 --- a/docs/MODULE-GUIDE.md +++ b/docs/MODULE-GUIDE.md @@ -28,7 +28,10 @@ people running the booth depend on them. ProdDash. If your feature needs a listener, a decoder or a worker, your server entry owns it: start it in `init()`, stop it in `stop()`, report it in `health()`. A child process the module itself spawns and supervises - is fine; a program someone launches by hand is not. + is fine; a program someone launches by hand is not. **A native helper — + anything that is not JavaScript — ships prebuilt inside the module folder, + for Apple silicon macOS and 64-bit Windows; nothing is ever compiled on + the machine running ProdDash** (see "Native helpers" below). 4. **Declare the ProdDash version you need** (`"proddash": ">=1.1.0"` in the manifest) and **bump your own `version`** whenever you change the module. The admin page refuses to install a module whose requirement the running @@ -83,6 +86,10 @@ modules// ├── module.json manifest (required) ├── client.js ES module, client entry (required) ├── server.js server entry (optional — only if you need server code) +├── bin/ native helpers, prebuilt per platform (only if you need one) +│ ├── -macos-arm64 +│ └── -win-x64.exe +├── native/ their source and the script that builds every bin/ file └── ... assets (css, images…) served statically at /modules// ``` @@ -97,6 +104,66 @@ the newer version runs. **Uninstall** in the admin page removes an installed module's files (a bundled one is hidden instead, since its folder belongs to the app); its settings are kept for a later reinstall. +### Native helpers + +Some features need a program JavaScript cannot be: reading an audio input, +decoding timecode, measuring sound. Rule 3 says the module owns that program; +this section says how it ships: **already built, for every platform it runs +on, inside the module folder.** The machine running ProdDash is a packaged +app or the Windows bundle plus modules from the admin page's catalog. It has +no compiler and never will — the catalog delivers exactly the folder that is +in the repo, so the binaries live in git next to the code. + +- **Platforms.** Apple silicon macOS and 64-bit Windows are required. Intel + macOS is welcome but optional. One file per platform in `bin/`, named for + what it is: + + | Platform | File | + | --- | --- | + | macOS, Apple silicon | `bin/-macos-arm64` | + | Windows, 64-bit | `bin/-win-x64.exe` | + | macOS, Intel (optional) | `bin/-macos-x86_64` | + +- **Pick it at runtime, and cope without it.** Choose by `process.platform` + and `process.arch`; when there is no file for this machine, the feature + says so — in `health()` and in the tile's `setStatus` — and everything else + in the module keeps working. Never try to build it, never look for a copy + elsewhere on the machine, never tell the operator to install anything. + + ```js + const HELPERS = { + darwin: { arm64: 'ltc-capture-macos-arm64', x64: 'ltc-capture-macos-x86_64' }, + win32: { x64: 'ltc-capture-win-x64.exe' }, + }; + const name = HELPERS[process.platform]?.[process.arch]; + const helper = name ? path.join(__dirname, 'bin', name) : ''; + if (!helper || !fs.existsSync(helper)) { + // Timecode is off on this platform. Say it once, keep the clock running. + } + ``` + +- **One contract, any language.** Every platform's binary speaks the same + command line and output format, so `server.js` has one code path. What is + behind it is your choice per platform — Swift against CoreAudio on macOS, + C#, C or Rust on Windows — the module does not care. +- **Source and build beside it.** Keep the source under `native/` with one + script that produces every `bin/` file from a checkout, so any binary can be + rebuilt from what is committed. A helper that cannot be rebuilt does not + ship. +- **Bump the module version whenever a binary changes**, like any other + file — that is how installs learn about it. +- **Keep them lean.** Optimised builds, no debug symbols, a few megabytes at + most: the catalog fetches the whole repo as one archive, capped at 80 MB. +- **Signing.** Apple silicon refuses unsigned code, so sign the macOS binary + — Developer ID when you have it, ad-hoc at least. Windows needs nothing: + files the app writes carry no download mark, so SmartScreen never sees + them. Permissions come from the launcher (see "Permissions macOS has to + grant"), not from the binary. + +A helper that ends up existing for one platform only is an exception made +on purpose, not a default: say so in the module's README and in the health +line, so a Windows booth knows why the feature is off. + ## module.json ```json