From 20d4e0f5244f2d166442690d425500a888f0cb85 Mon Sep 17 00:00:00 2001 From: Waters Church Date: Mon, 14 Sep 2026 16:31:02 -0400 Subject: [PATCH] Shell: settings window sized to its content, with a slim grab bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No title and no ✕ — the window sits beside its tile and closes on a click anywhere else or Escape — and it is as wide as its fields want, so a couple of switches make a small window. Help lines wrap to the fields' width instead of widening it. Guide: one control per action — whatever has a header button is not offered again in the gear. ProdCom 1.2.2 drops its timestamps and newest-first gear switches (both are header buttons). Co-Authored-By: Claude Fable 5.1 --- docs/MODULE-GUIDE.md | 18 ++++---- modules/prodcom-transcript/module.json | 32 +++++++------- public/shell.js | 18 +++----- public/style.css | 60 ++++++++++---------------- 4 files changed, 54 insertions(+), 74 deletions(-) diff --git a/docs/MODULE-GUIDE.md b/docs/MODULE-GUIDE.md index 16c7927..3b12394 100644 --- a/docs/MODULE-GUIDE.md +++ b/docs/MODULE-GUIDE.md @@ -184,9 +184,10 @@ explanation there. popover (`collapsed: true | false`, `columns`, `help`, `toggle`). That is how a module with a long show/hide checklist keeps the popover usable — see `pco-plan`. Display choices belong there, per tile; admin config is for -connections. The popover itself is a small window beside the tile (moved by -its bar, closed by clicking away) so a tile never has to be big enough to -hold its own settings, and a change can be watched as it lands. +connections. The popover itself is a small window beside the tile — sized to +its content, moved by the slim bar on top, closed by clicking anywhere else +or pressing Escape — so a tile never has to be big enough to hold its own +settings, and a change can be watched as it lands. A picker entry (see **Presenting multiple tiles**) may carry its own `instanceSchema` and `instanceGroups`: tiles added from that entry get those @@ -350,11 +351,12 @@ Everything you add is removed automatically when your instance stops, so a remount never duplicates controls. Keep them compact (icon buttons, short labels): the title bar is 32 px tall and shared with the tile's name. -**Text size lives here, not in the gear popover.** A module whose text can -be sized offers A− / A+ header buttons that persist through -`saveInstanceSettings({ textSize })` and reads the value back with a sane -fallback; it does not also expose a "Text size" number field in -`instanceSchema` — one control per setting. +**One control per action.** Whatever has a dedicated header button is not +offered again in the gear popover: text size is A− / A+ up here (persisted +through `saveInstanceSettings({ textSize })`, read back with a sane +fallback), never a "Text size" field in `instanceSchema`; a timestamps or +newest-first toggle in the header means no such switch in the gear. The +gear is for display choices that have no button of their own. One more thing to know: the user can hide the whole title bar with the small notch on its bottom edge (a clean-view mode for wall displays). Your diff --git a/modules/prodcom-transcript/module.json b/modules/prodcom-transcript/module.json index 8de270a..cf5250b 100644 --- a/modules/prodcom-transcript/module.json +++ b/modules/prodcom-transcript/module.json @@ -1,22 +1,34 @@ { "id": "prodcom-transcript", "name": "ProdCom", - "version": "1.2.1", + "version": "1.2.2", "proddash": ">=1.1.0", "description": "Live ProdCom transcript stream", "permissions": [ - { "kind": "local-network", "reason": "Reaching the ProdCom API on this network" } + { + "kind": "local-network", + "reason": "Reaching the ProdCom API on this network" + } ], "client": "client.js", "server": "server.js", "style": "style.css", - "minSize": { "w": 3, "h": 2 }, - "defaultSize": { "w": 4, "h": 4 }, + "minSize": { + "w": 3, + "h": 2 + }, + "defaultSize": { + "w": 4, + "h": 4 + }, "configSchema": { "prodcom": { "type": "endpoint", "label": "ProdCom server", - "default": { "host": "10.3.11.152", "port": 24480 } + "default": { + "host": "10.3.11.152", + "port": 24480 + } }, "apiKey": { "type": "password", @@ -29,16 +41,6 @@ "type": "boolean", "label": "Show channel icons", "default": true - }, - "newestFirst": { - "type": "boolean", - "label": "Newest first (transcript reads bottom-up)", - "default": false - }, - "showTimes": { - "type": "boolean", - "label": "Show timestamps", - "default": false } } } diff --git a/public/shell.js b/public/shell.js index 16ec6a7..5ca3b32 100644 --- a/public/shell.js +++ b/public/shell.js @@ -1157,20 +1157,12 @@ function toggleSettingsPopover(tile, el) { pop.setAttribute('role', 'dialog'); pop.setAttribute('aria-label', `${tile.title || man.name} settings`); - // A bar to move it by, and the one control every window has. + // A slim bar to move it by — nothing else. No title (the window sits beside + // the tile it belongs to) and no close button: a click anywhere else, or + // Escape, closes it. const bar = document.createElement('div'); bar.className = 'tile-settings-bar'; - const barTitle = document.createElement('span'); - barTitle.className = 'tile-settings-title'; - barTitle.textContent = tile.title || man.name; - const closeBtn = document.createElement('button'); - closeBtn.type = 'button'; - closeBtn.className = 'tile-settings-close'; - closeBtn.title = 'Close'; - closeBtn.setAttribute('aria-label', 'Close settings'); - closeBtn.textContent = '✕'; - closeBtn.addEventListener('click', () => closeSettingsPopovers()); - bar.append(barTitle, closeBtn); + bar.title = 'Drag to move'; const body = document.createElement('div'); body.className = 'tile-settings-body'; pop.append(bar, body); @@ -1486,7 +1478,7 @@ function toggleSettingsPopover(tile, el) { // Moved by its bar. bar.addEventListener('pointerdown', (e) => { - if (e.target === closeBtn || (e.button !== 0 && e.pointerType === 'mouse')) return; + if (e.button !== 0 && e.pointerType === 'mouse') return; e.preventDefault(); bar.setPointerCapture(e.pointerId); const dx = e.clientX - pop.offsetLeft; diff --git a/public/style.css b/public/style.css index 859e33e..8accbef 100644 --- a/public/style.css +++ b/public/style.css @@ -542,7 +542,10 @@ body.fullscreen #grid { padding-bottom: 0; } z-index: 80; display: flex; flex-direction: column; - min-width: 260px; + /* as wide as its content wants and no wider: a couple of switches make a + small window, a three-column checklist a wide one */ + width: max-content; + min-width: 150px; max-width: min(560px, calc(100vw - 16px)); max-height: calc(100vh - 16px); overflow: hidden; @@ -551,12 +554,13 @@ body.fullscreen #grid { padding-bottom: 0; } border-radius: 10px; box-shadow: 0 12px 36px rgba(0, 0, 0, 0.55); } +/* the grab bar: a slim strip with a grip mark, nothing to read or press */ .tile-settings-bar { flex: none; + height: 14px; display: flex; align-items: center; - gap: 8px; - padding: 6px 6px 6px 12px; + justify-content: center; background: rgba(255, 255, 255, 0.04); border-bottom: 1px solid var(--border); cursor: grab; @@ -564,36 +568,28 @@ body.fullscreen #grid { padding-bottom: 0; } -webkit-user-select: none; touch-action: none; } +.tile-settings-bar::before { + content: ""; + width: 34px; + height: 4px; + border-radius: 2px; + background: var(--muted); + opacity: 0.55; + transition: opacity 0.12s; +} +.tile-settings-bar:hover::before { opacity: 0.9; } .tile-settings-bar:active { cursor: grabbing; } -.tile-settings-title { - flex: 1; - min-width: 0; - font-size: 12px; - font-weight: 700; - letter-spacing: 0.03em; - color: var(--muted); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.tile-settings-close { - flex: none; - width: 24px; - height: 24px; - border: none; - border-radius: 6px; - background: transparent; - color: var(--muted); - font-size: 12px; - cursor: pointer; -} -.tile-settings-close:hover { background: rgba(255, 255, 255, 0.08); color: var(--text); } .tile-settings-body { flex: 1; min-height: 0; overflow-y: auto; padding: 10px; } +/* Help lines never widen the window: they take no room of their own when the + content is measured, then wrap to whatever width the fields settled on. */ +.tile-settings .field-help, +.tile-settings .field-group-help, +.tile-settings .multiselect-empty { width: 0; min-width: 100%; } /* a group's on/off sits in its title row; the body dims while it is off */ .tile-settings .field-group-title { display: flex; align-items: center; gap: 8px; } .tile-settings .field-group-name { flex: 1; min-width: 0; } @@ -633,12 +629,6 @@ body.fullscreen #grid { padding-bottom: 0; } .tile-settings .template-var:hover { border-color: var(--accent); } .tile-settings .template-var:active { cursor: grabbing; } -/* A popover with grouped settings needs room for its columns; it still - never grows past the tile (max-width above). */ -.tile-settings.has-groups { - min-width: min(540px, calc(100vw - 16px)); - container-type: inline-size; -} .tile-settings .field-group { margin: 0 0 10px; padding: 8px 10px; @@ -688,12 +678,6 @@ body.fullscreen #grid { padding-bottom: 0; } many columns the group lays its other fields out in */ .tile-settings .field-group-body.is-columns .multiselect-field, .tile-settings .field-group-body.is-columns .template-field { grid-column: 1 / -1; } -@container (max-width: 420px) { - .tile-settings .field-group-body.is-columns { grid-template-columns: 1fr 1fr; } -} -@container (max-width: 260px) { - .tile-settings .field-group-body.is-columns { grid-template-columns: 1fr; } -} .tile-settings .field-group-help, .tile-settings .field-help { display: block;