From 148116ae897b0d998d837177cd44007280f57bc0 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Wed, 3 Jun 2026 18:58:12 -0400 Subject: [PATCH] zebra-spaces: kick + ban land on their own row beneath primary mod actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Destructive controls shouldn't share a row with role-transition buttons — the muscle-memory misclick where 'ban' sat next to '→ speaker' is exactly the kind of mod ergonomics fox flagged. Kick + ban now render inside a nested .acts-removal grid that spans the parent auto-fit row, so they always wrap to a fresh line below stream / → cohost / → speaker / → listener regardless of available width. --- web/zebra-spaces.html | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 6afa1dd..d002c0a 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -542,6 +542,19 @@ margin-top: 0.2rem; } .mod-actions:empty { display: none; } /* no row gap when nothing to do */ + /* kick + ban always land on their own row beneath the primary mod + * actions (stream toggle, role transitions). They're destructive, + * not routine — visually separating them avoids the muscle-memory + * misclick where 'ban' sits next to '→ speaker' and a quick stab + * removes someone from the room. grid-column:1/-1 spans the parent + * auto-fit row so the next auto-placed item wraps. */ + .mod-actions .acts-removal { + grid-column: 1 / -1; + display: grid; + grid-template-columns: repeat(auto-fit, max-content); + gap: 0.3rem; + margin-top: 0.15rem; + } /* log lives in its own full-width section beneath the .page grid. * 240px default, vertical resize handle so the user can drag it * taller when the chatter outpaces the viewport. */ @@ -4073,16 +4086,22 @@ function renderRoom(){ /* kick + ban allowed against anyone except host; cohosts also * can't kick/ban cohosts (host only). Two buttons because the * actions have different blast radius: kick = drop this session - * (rejoinable), ban = block-by-pubkey for the room's hold window. */ + * (rejoinable), ban = block-by-pubkey for the room's hold window. + * Nested in their own .acts-removal row so they always sit on a + * line beneath the primary mod actions — destructive controls + * shouldn't share a row with role-transition buttons. */ if (m.role !== 'host' && !(m.role === 'cohost' && myRole !== 'host')){ + const removalRow = document.createElement('span'); + removalRow.className = 'acts-removal'; const kb = document.createElement('button'); kb.className='small'; kb.textContent = 'kick'; kb.onclick = () => modKick(m.uuid).catch(e => logLine('err','kick: '+e.message)); - acts.appendChild(kb); + removalRow.appendChild(kb); const bb = document.createElement('button'); bb.className='small'; bb.textContent = 'ban'; bb.onclick = () => modBan(m.uuid).catch(e => logLine('err','ban: '+e.message)); - acts.appendChild(bb); + removalRow.appendChild(bb); + acts.appendChild(removalRow); } } row.appendChild(badge); row.appendChild(handle); row.appendChild(pub); @@ -4555,8 +4574,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');