Shell: settings window sized to its content, with a slim grab bar

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 <noreply@anthropic.com>
This commit is contained in:
Waters Church
2026-09-14 16:31:02 -04:00
co-authored by Claude Fable 5.1
parent 5dd1b734aa
commit 20d4e0f524
4 changed files with 54 additions and 74 deletions
+5 -13
View File
@@ -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;
+22 -38
View File
@@ -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;