[object Object]

← 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 →