← back to Desktop Dotbar
dotbar: visible dotted resize grip + one-click Compact/Full toggle button
fa809cbe90d3c72ee535d22d3b7609cf7344e80b · 2026-09-23 09:39:15 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VyZZwbLBMfoBmdLGuxBuD
Files touched
Diff
commit fa809cbe90d3c72ee535d22d3b7609cf7344e80b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 23 09:39:15 2026 -0700
dotbar: visible dotted resize grip + one-click Compact/Full toggle button
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VyZZwbLBMfoBmdLGuxBuD
---
public/index.html | 50 +++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 43 insertions(+), 7 deletions(-)
diff --git a/public/index.html b/public/index.html
index 5bc7f32..f683a7f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -36,19 +36,23 @@
body.orient-top #grip { left:0; right:0; top: calc(var(--bar-h) - 8px); height:8px; cursor:ns-resize; }
body.orient-left #grip { top:0; bottom:0; left: calc(var(--bar-w) - 8px); width:8px; cursor:ew-resize; }
body.orient-right #grip { top:0; bottom:0; right: calc(var(--bar-w) - 8px); width:8px; cursor:ew-resize; }
- #grip::after { content:""; position:absolute; border-radius:2px; background:var(--line); opacity:.5; }
- body.orient-top #grip::after { left:50%; bottom:2px; width:46px; height:2px; transform:translateX(-50%); }
- body.orient-left #grip::after, body.orient-right #grip::after { top:50%; left:2px; width:2px; height:46px; transform:translateY(-50%); }
- #grip:hover::after { opacity:1; background:var(--dim); }
+ /* Visible grab-handle (Steve 2026-09-23): a persistent DOTTED handle so the resize grip is
+ discoverable, not an invisible hover strip. Baseline .75 opacity, brightens to --fg on hover. */
+ #grip::after { content:""; position:absolute; border-radius:3px; opacity:.75; background:var(--dim); }
+ body.orient-top #grip::after { left:50%; bottom:1px; width:58px; height:4px; transform:translateX(-50%);
+ background-image: repeating-linear-gradient(90deg, var(--bg) 0 2px, transparent 2px 6px); }
+ body.orient-left #grip::after, body.orient-right #grip::after { top:50%; left:2px; width:4px; height:58px; transform:translateY(-50%);
+ background-image: repeating-linear-gradient(0deg, var(--bg) 0 2px, transparent 2px 6px); }
+ #grip:hover::after { opacity:1; background:var(--fg); }
/* Right-end LENGTH grip — TOP dock only: drag the bar's right edge to contract/expand its horizontal
span (ew-resize), left-anchored. Hidden on side docks (there the main #grip changes width). Stops
8px short of the bottom so it never fights the height grip in the corner. Steve 2026-09-17. */
#gripx { position:fixed; z-index:50; -webkit-app-region:no-drag; display:none; }
body.orient-top #gripx { display:block; top:0; right:0; width:8px; height: calc(var(--bar-h) - 8px); cursor:ew-resize; }
- #gripx::after { content:""; position:absolute; right:2px; top:20%; height:60%; width:2px; border-radius:2px;
- background:var(--line); opacity:.5; }
- #gripx:hover::after { opacity:1; background:var(--dim); }
+ #gripx::after { content:""; position:absolute; right:2px; top:20%; height:60%; width:4px; border-radius:3px;
+ opacity:.75; background:var(--dim); background-image: repeating-linear-gradient(0deg, var(--bg) 0 2px, transparent 2px 6px); }
+ #gripx:hover::after { opacity:1; background:var(--fg); }
.chip { -webkit-app-region:no-drag; cursor:pointer; display:flex; align-items:center; gap:6px;
padding:6px 10px; border-radius:9px; border:1px solid transparent; line-height:1; white-space:nowrap; }
@@ -106,6 +110,10 @@
<div id="panel"></div>
<script>
const BAR_H = 48, PANEL_H = 360;
+// One-click compact/full toggle presets (Steve 2026-09-23). COMPACT_W = the side-dock min
+// (icon-only strip); COMPACT_LEN = a short top-dock length. "Full" restores the user's last
+// dragged size (persisted in localStorage) so the toggle returns exactly where they had it.
+const COMPACT_W = 120, FULL_W_DEFAULT = 210, COMPACT_LEN = 320;
let openColor = null, data = null, _miss = 0, curBarH = BAR_H, curBarW = 210, curBarLen = 0, arranging = false, arrangeMsg = '';
// Live config from the main process (orientation + auto-hide + top-bar length). Defaults match a fresh install.
let cfg = { orientation: 'top', autohide: false, barW: 210, barLen: 0 };
@@ -171,6 +179,7 @@ function renderBar(){
meta.innerHTML = `<button class="arrbtn${arranging?' busy':''}" ${arranging?'disabled':''} title="run the master dot-screen arrangement now" onclick="arrange()">${arranging?'⧉ Arranging…':(arrangeMsg?('⧉ '+arrangeMsg):'⧉ Arrange master')}</button>`
+ `<button class="cfgbtn" title="dock the bar on another edge (revert to Top from a side)" onclick="cycleOrient()">${ORIENT_LABEL[cfg.orientation]||'▲ Top'} ▸ ${ORIENT_LABEL[nextO].replace(/^[^ ]+ /,'')}</button>`
+ `<button class="cfgbtn${cfg.autohide?' on':''}" title="edge auto-hide: slide the bar off its edge; hover the edge to reveal" onclick="toggleAutohide()">${cfg.autohide?'👁 Auto-hide':'📌 Pinned'}</button>`
+ + `<button class="cfgbtn" title="one-click: snap between a compact (icon-only) bar and full width" onclick="toggleCompact()">${isCompact()?'⤢ Expand':'⤡ Compact'}</button>`
+ `<span><b>${data.total}</b> live</span>`
+ `<span class="icobtn" title="refresh" onclick="tick()">⟳</span>`
+ `<span class="icobtn" title="quit bar" onclick="(window.dotbar&&window.dotbar.quit)&&window.dotbar.quit()">✕</span>`;
@@ -243,6 +252,33 @@ async function reveal(tty){
// TOP bar (Steve TK-11885). The main process owns bounds + persistence; here we just ask it.
function cycleOrient(){ if (window.dotbar && window.dotbar.cycleOrientation) window.dotbar.cycleOrientation(); }
function toggleAutohide(){ if (window.dotbar && window.dotbar.setAutohide) window.dotbar.setAutohide(!cfg.autohide); }
+// One-click compact/full toggle (Steve 2026-09-23). Targets the resizable axis for the current
+// dock — barLen for a TOP strip, barW for a SIDE strip — reusing the existing setBarLen/setBarW IPC.
+function isCompact(){
+ if (cfg.orientation === 'top') { const l = cfg.barLen; return !!(l && l > 0 && l <= COMPACT_LEN + 20); }
+ return curBarW <= COMPACT_W + 20;
+}
+function toggleCompact(){
+ if (!window.dotbar) return;
+ if (cfg.orientation === 'top') {
+ const cur = (cfg.barLen && cfg.barLen > 0) ? cfg.barLen : (window.innerWidth || 0);
+ if (!isCompact() && window.dotbar.setBarLen) { // full -> compact (remember where we were)
+ if (cur > COMPACT_LEN) { try { localStorage.setItem('dotbar.fullLen', String(cur)); } catch(_){} }
+ window.dotbar.setBarLen(COMPACT_LEN);
+ } else if (window.dotbar.setBarLen) { // compact -> full (0 = span the screen)
+ const full = parseInt(localStorage.getItem('dotbar.fullLen') || '0', 10);
+ window.dotbar.setBarLen(full > COMPACT_LEN ? full : 0);
+ }
+ } else {
+ if (!isCompact() && window.dotbar.setBarW) { // full -> compact
+ if (curBarW > COMPACT_W) { try { localStorage.setItem('dotbar.fullW', String(curBarW)); } catch(_){} }
+ window.dotbar.setBarW(COMPACT_W);
+ } else if (window.dotbar.setBarW) { // compact -> full (restore last, else default)
+ const full = parseInt(localStorage.getItem('dotbar.fullW') || String(FULL_W_DEFAULT), 10);
+ window.dotbar.setBarW(full > COMPACT_W ? full : FULL_W_DEFAULT);
+ }
+ }
+}
async function arrange(){
if (arranging) return;
← 60eea92 Fix RAM missing on the bar: move RAM/CPU readout into the al
·
back to Desktop Dotbar
·
dotbar: RAM chip shows real memory pressure, not cache-infla b05080a →