# ProdDash Module Guide How to build, install and test a ProdDash module. Everything a module author needs is on this page — you never have to read the shell's source. A **module** is one kind of dashboard tile: the live transcript, the Now/Next display, a clock. Users add any number of tiles of your module to their own dashboards, move and resize them, and give each tile its own per-tile settings. Your module renders inside the tile and talks to the outside world only through the small API the shell hands it. ## Rules every module follows These are not style preferences — the admin page, the installer and the people running the booth depend on them. 1. **All settings live in the ProdDash admin page.** Server-wide settings go in `configSchema`, per-tile settings in `instanceSchema`. Never a separate setup page, wizard, config file the operator edits by hand, or environment variable. 2. **Dependencies ship inside the module folder.** Zero dependencies is the norm (the shell has none). Anything you genuinely need is vendored into the module — or fetched by your own `init()` on first start, with the admin health line saying what is happening. No `npm install`, no installer, no "run this first". 3. **Everything the module does runs inside the module.** No helper scripts, companion programs, daemons or services the operator has to start next to 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. 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 shell doesn't meet, and it offers updates by comparing versions. ## The shape of a module A module is one folder dropped into `modules/`: ``` modules// ├── module.json manifest (required) ├── client.js ES module, client entry (required) ├── server.js server entry (optional — only if you need server code) └── ... assets (css, images…) served statically at /modules// ``` **Installation is exactly this:** in the admin page, open **Available modules** and press **Install** — the module is downloaded from the ProdDash repo into the server's data directory (`/modules//`) and starts immediately. Developing locally? Drop the folder into `modules/` (bundled with the checkout) or into the data directory's `modules/` and restart. No shell code changes, ever. Modules bundled with a checkout and modules installed from the admin page live side by side; when both have the same id 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. ## module.json ```json { "id": "my-module", "name": "My Module", "version": "1.0.0", "proddash": ">=1.1.0", "description": "One line shown in the Add-tile picker and the admin page", "permissions": [ { "kind": "microphone", "reason": "Why this module needs it, in one line" } ], "client": "client.js", "server": "server.js", "style": "style.css", "minSize": { "w": 2, "h": 2 }, "defaultSize": { "w": 4, "h": 3 }, "configSchema": { "upstreamUrl": { "type": "string", "label": "Upstream URL", "default": "http://10.0.0.5:1234" }, "apiKey": { "type": "password", "label": "API key", "default": "" } }, "instanceSchema": { "channel": { "type": "string", "label": "Channel filter", "default": "" } } } ``` | Field | Required | Meaning | | --- | --- | --- | | `id` | yes | Must equal the folder name. Lowercase, dashes. | | `name` | yes | Human name shown on the tile header and in menus. | | `version` | yes | Your module's version (`x.y.z`). Bump it on every change — the admin page offers updates when the repo's copy is newer than the installed one. | | `proddash` | yes | The ProdDash versions this module works with: `">=1.1.0"`, `"^1.1.0"`, `">=1.1.0 <2.0.0"`, or `"*"`. A module whose requirement the running shell doesn't meet is listed in the admin page but never loaded, and can't be installed from the catalog. | | `description` | no | Shown in the admin page / picker. | | `permissions` | no | What macOS must allow before this module can work — see **Permissions macOS has to grant**. Ignored by the shell; read by the macOS launcher. | | `provides` | no | Capabilities other modules can consume — `["timers"]` today. Forwarded in `/api/modules` so a consumer can find you. See **Working with other modules**. | | `client` | yes | Client entry file, loaded as an ES module. | | `server` | no | Server entry file (CommonJS), loaded with `require()`. | | `style` | no | A stylesheet the shell injects once per module. | | `minSize`, `defaultSize` | no | Tile size in grid cells (12 columns wide; rows are ~72 px). Defaults: min 1×1, default 4×3. | | `configSchema` | no | **Admin config** — server-wide settings (URLs, ports, keys) edited once at `/admin` for everyone. | | `instanceSchema` | no | **Per-tile settings** — chosen in each tile's gear menu and stored with that browser's layout (filters, text sizes, display options). They apply as they are changed, so keep them to display choices. | | `tiles` | no | Fixed multi-tile list for client-only modules; server modules export a dynamic `tiles()` instead. See **Presenting multiple tiles**. | Both schemas map field names to specs: ``` { "type": "string" | "text" | "number" | "boolean" | "switch" | "select" | "color" | "password" | "endpoint", "label": "Shown next to the field", "default": , "group": "Section heading", // optional; see below "showWhen": "", // optional; see below "help": "One line shown under the control", // optional, admin config only "options": [ { "value": "a", "label": "A", "group": "Optgroup" }, … ], // "select" only "optionsRoute": "/devices", // "select" only, admin config only "optionsDependsOn": "" // with optionsRoute; see below } ``` `boolean` and `switch` are the same true/false value. In a tile's gear popover both render as a switch; in the admin page `switch` is the toggle and `boolean` the plainer checkbox (use `switch` for a feature on/off that other fields depend on). **Label a switch with the noun of what it turns on** — "Seconds", "Top bar", "Section headers" — never "Show seconds": on means shown or enabled, off means hidden or disabled, and the label shouldn't say so twice. `color` renders a swatch picker and stores a `#rrggbb` string (give it a hex default; the tile applies it as a CSS custom property — see `pco-plan`). `text` is a multi-line string — a `