A layout keeps every tile it was given, and a tile whose module no longer offers its picker entry keeps its place and draws nothing — deliberately, so a Service timer can wait for PCO Plan. But that tile still holds its grid cells for collision, and nothing anywhere could delete a tile that is not drawn. The Send Message rollout (#40) made this bite: Slack stopped offering send and quick, so every booth layout now carries invisible Send message / Quick replies tiles locking their space.
The change
Layouts menu → Not shown section (public/shell.js, public/style.css), present only while the layout holds a tile the board is not drawing:
One row per tile: its title (falling back to the module's name plus the variant id, or the module id when the module is not installed), a muted reason — not offered right now, module disabled, module needs a newer ProdDash, module not installed — and a drawn ✕ (inline SVG, tooltip "Remove", aria-label) that calls the existing removeTile(), which saves the layout and frees the cells, exactly as a drawn tile's ✕ does (no toast there either).
The menu stays open after a remove, like the Grid Size stepper and the Alerts fold, so the next row (or none) is seen.
The section repaints while the menu is open from reconcileOfferedTiles(), so a row leaves the moment its tile is drawn again (module re-enabled, entry offered again) — and only when the list actually changed, so the 6-second registry recheck does not rebuild rows under the pointer.
The toast after loading a named layout now ends "…aren't offered right now; the Layouts menu can remove them".
Existing menu classes and CSS variables only (--muted, --danger, --border); the heading matches "Load from server".
GET /api/modules returns inactive (server.js): the installed modules that are not running, as { id, name, reason: 'disabled' | 'incompatible' }. The dashboard's registry only ever held active modules, so without this the client could not tell "module disabled" from "module not installed". Id and name only — none of a module's config travels with it. The reconcile/remount logic is unchanged (registry still holds active manifests only).
Docs and version: package.json 1.12.0 → 1.13.0; README's Layouts-menu paragraph and toast quote; module guide's "Presenting multiple tiles" bullet (the operator can remove a retired entry's tile) and the GET /api/modules line.
A tile that merely waits for its module is listed too, but nothing happens to it unless the operator removes it — the keep-its-place behaviour is untouched.
Testing
Run from a scratch data directory on :24500 with the app's browser preview:
Layout with a shown Service Times tile, a shown Slack transcript tile, and four not-shown tiles: Slack send and quick (Slack configured with one channel, so it offers only transcript:…), a Clock tile with Clock disabled in admin, and a tile for a module that does not exist. The menu listed all four with the right reasons; the board showed the dead area to the right.
✕ removed Send message: row gone, menu stayed open, localStorage['proddash:layout'] dropped the tile, tooltip "Remove" shown. Reload kept the removal.
Re-enabling Clock from the admin API with the menu open: its row left on the modules-changed event and the Clock tile drew in its kept place.
Removing the last two rows made the section disappear with the menu still open. Adding a Clock from the + picker then landed at column 4, row 0 — the cells the removed Send message tile had held.
Saved a named layout carrying send/quick and loaded it: toast reads Loaded "Test": 3 tiles, 2 not shown — Send message, Quick replies aren't offered right now; the Layouts menu can remove them with Undo.
No browser console errors, no server errors in the log.
node tools/layout-roundtrip.test.js, node tools/unbundled.test.js, node tools/update-check.test.js: every check passed.
.claude/launch.json was used with a temporary dev entry and restored from git before committing. No module files touched.
## The problem
A layout keeps every tile it was given, and a tile whose module no longer offers its picker entry keeps its place and draws nothing — deliberately, so a Service timer can wait for PCO Plan. But that tile still holds its grid cells for collision, and nothing anywhere could delete a tile that is not drawn. The Send Message rollout (#40) made this bite: Slack stopped offering `send` and `quick`, so every booth layout now carries invisible Send message / Quick replies tiles locking their space.
## The change
**Layouts menu → Not shown section** (`public/shell.js`, `public/style.css`), present only while the layout holds a tile the board is not drawing:
- One row per tile: its title (falling back to the module's name plus the variant id, or the module id when the module is not installed), a muted reason — *not offered right now*, *module disabled*, *module needs a newer ProdDash*, *module not installed* — and a drawn ✕ (inline SVG, tooltip "Remove", `aria-label`) that calls the existing `removeTile()`, which saves the layout and frees the cells, exactly as a drawn tile's ✕ does (no toast there either).
- The menu stays open after a remove, like the Grid Size stepper and the Alerts fold, so the next row (or none) is seen.
- The section repaints while the menu is open from `reconcileOfferedTiles()`, so a row leaves the moment its tile is drawn again (module re-enabled, entry offered again) — and only when the list actually changed, so the 6-second registry recheck does not rebuild rows under the pointer.
- The toast after loading a named layout now ends "…aren't offered right now; the Layouts menu can remove them".
- Existing menu classes and CSS variables only (`--muted`, `--danger`, `--border`); the heading matches "Load from server".
**`GET /api/modules` returns `inactive`** (`server.js`): the installed modules that are not running, as `{ id, name, reason: 'disabled' | 'incompatible' }`. The dashboard's registry only ever held active modules, so without this the client could not tell "module disabled" from "module not installed". Id and name only — none of a module's config travels with it. The reconcile/remount logic is unchanged (`registry` still holds active manifests only).
**Docs and version**: `package.json` 1.12.0 → 1.13.0; README's Layouts-menu paragraph and toast quote; module guide's "Presenting multiple tiles" bullet (the operator can remove a retired entry's tile) and the `GET /api/modules` line.
A tile that merely waits for its module is listed too, but nothing happens to it unless the operator removes it — the keep-its-place behaviour is untouched.
## Testing
Run from a scratch data directory on :24500 with the app's browser preview:
- Layout with a shown Service Times tile, a shown Slack transcript tile, and four not-shown tiles: Slack `send` and `quick` (Slack configured with one channel, so it offers only `transcript:…`), a Clock tile with Clock disabled in admin, and a tile for a module that does not exist. The menu listed all four with the right reasons; the board showed the dead area to the right.
- ✕ removed Send message: row gone, menu stayed open, `localStorage['proddash:layout']` dropped the tile, tooltip "Remove" shown. Reload kept the removal.
- Re-enabling Clock from the admin API with the menu open: its row left on the `modules-changed` event and the Clock tile drew in its kept place.
- Removing the last two rows made the section disappear with the menu still open. Adding a Clock from the + picker then landed at column 4, row 0 — the cells the removed Send message tile had held.
- Saved a named layout carrying `send`/`quick` and loaded it: toast reads *Loaded "Test": 3 tiles, 2 not shown — Send message, Quick replies aren't offered right now; the Layouts menu can remove them* with Undo.
- No browser console errors, no server errors in the log.
- `node tools/layout-roundtrip.test.js`, `node tools/unbundled.test.js`, `node tools/update-check.test.js`: every check passed.
`.claude/launch.json` was used with a temporary dev entry and restored from git before committing. No module files touched.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A tile whose module stopped offering its picker entry — Slack's Send message
and Quick replies since the Send Message module took them over — kept its
place in the layout and drew nothing, as designed, but still held its grid
cells with no control anywhere to let them go: a dead area the operator
could not place anything in.
The Layouts menu now has a Not shown section, present only while the layout
holds such a tile. One row per tile: its title (or the module's name plus
the variant, or the module id when it is not installed), why in a few words
— "not offered right now", "module disabled", "module needs a newer
ProdDash", "module not installed" — and a drawn ✕ with a tooltip that
removes it, saves the layout and frees the cells, leaving the menu open as
the Grid Size stepper and Alerts do. The section repaints while the menu is
open whenever tiles become offered or stop being, and only when the list
changed. A tile that merely waits for its module keeps its place as before.
GET /api/modules also returns `inactive` — the installed modules that are
not running, id, name and reason only — so a dashboard can tell a disabled
module from one that is not installed. The toast after loading a named
layout now says the Layouts menu can remove the tiles it did not show.
README and the module guide updated.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
michael
merged commit 1410cbc169 into main2026-10-11 09:54:07 -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.
The problem
A layout keeps every tile it was given, and a tile whose module no longer offers its picker entry keeps its place and draws nothing — deliberately, so a Service timer can wait for PCO Plan. But that tile still holds its grid cells for collision, and nothing anywhere could delete a tile that is not drawn. The Send Message rollout (#40) made this bite: Slack stopped offering
sendandquick, so every booth layout now carries invisible Send message / Quick replies tiles locking their space.The change
Layouts menu → Not shown section (
public/shell.js,public/style.css), present only while the layout holds a tile the board is not drawing:aria-label) that calls the existingremoveTile(), which saves the layout and frees the cells, exactly as a drawn tile's ✕ does (no toast there either).reconcileOfferedTiles(), so a row leaves the moment its tile is drawn again (module re-enabled, entry offered again) — and only when the list actually changed, so the 6-second registry recheck does not rebuild rows under the pointer.--muted,--danger,--border); the heading matches "Load from server".GET /api/modulesreturnsinactive(server.js): the installed modules that are not running, as{ id, name, reason: 'disabled' | 'incompatible' }. The dashboard's registry only ever held active modules, so without this the client could not tell "module disabled" from "module not installed". Id and name only — none of a module's config travels with it. The reconcile/remount logic is unchanged (registrystill holds active manifests only).Docs and version:
package.json1.12.0 → 1.13.0; README's Layouts-menu paragraph and toast quote; module guide's "Presenting multiple tiles" bullet (the operator can remove a retired entry's tile) and theGET /api/modulesline.A tile that merely waits for its module is listed too, but nothing happens to it unless the operator removes it — the keep-its-place behaviour is untouched.
Testing
Run from a scratch data directory on :24500 with the app's browser preview:
sendandquick(Slack configured with one channel, so it offers onlytranscript:…), a Clock tile with Clock disabled in admin, and a tile for a module that does not exist. The menu listed all four with the right reasons; the board showed the dead area to the right.localStorage['proddash:layout']dropped the tile, tooltip "Remove" shown. Reload kept the removal.modules-changedevent and the Clock tile drew in its kept place.send/quickand loaded it: toast reads Loaded "Test": 3 tiles, 2 not shown — Send message, Quick replies aren't offered right now; the Layouts menu can remove them with Undo.node tools/layout-roundtrip.test.js,node tools/unbundled.test.js,node tools/update-check.test.js: every check passed..claude/launch.jsonwas used with a temporary dev entry and restored from git before committing. No module files touched.🤖 Generated with Claude Code