← back to Desktop Dotbar
dotbar: RAM chip shows real memory pressure, not cache-inflated used% (96%->44%) + green/amber/red bands
b05080a61ccbb81df0dc40e0f6866c3046889bf3 · 2026-09-23 09:51:33 -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
M public/index.htmlM server.js
Diff
commit b05080a61ccbb81df0dc40e0f6866c3046889bf3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 23 09:51:33 2026 -0700
dotbar: RAM chip shows real memory pressure, not cache-inflated used% (96%->44%) + green/amber/red bands
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VyZZwbLBMfoBmdLGuxBuD
---
public/index.html | 7 +++++--
server.js | 19 ++++++++++++++-----
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/public/index.html b/public/index.html
index f683a7f..d971a5d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -119,6 +119,9 @@ let openColor = null, data = null, _miss = 0, curBarH = BAR_H, curBarW = 210, cu
let cfg = { orientation: 'top', autohide: false, barW: 210, barLen: 0 };
// RAM Sniper readout (RAM%/CPU% + daemon on/off + top hogs), polled from /api/ram.
let ram = { ram_pct: '–', cpu_pct: '–', on: false, hogs: [] };
+// RAM pressure bands (Steve 2026-09-23): green < 50, amber < 75, red >= 75. Input is the
+// memory-pressure-based used% from the server, so these thresholds mean real strain, not cache.
+function ramColor(p){ p = parseFloat(p); if (isNaN(p)) return 'var(--fg)'; return p < 50 ? '#4ade80' : (p < 75 ? '#fbbf24' : '#f87171'); }
// The four needs-Steve states pulse; green/pink stay solid (Steve 2026-09-15 dot-flash directive).
const WAIT = new Set(['lightblue','orange','purple','yellow']);
const ORIENT_LABEL = { top: '▲ Top', left: '◀ Left', right: '▶ Right' };
@@ -163,8 +166,8 @@ function renderBar(){
const rchip = document.createElement('div');
rchip.className = 'chip ramchip' + (openColor==='ram' ? ' active':'');
rchip.title = 'RAM / CPU — click for top memory hogs';
- rchip.innerHTML = `<span class="dot" style="background:#59d0ff"></span>`
- + `<span class="cnt">🧠 ${ram.ram_pct}% <span style="color:var(--dim);font-weight:600">⚙ ${ram.cpu_pct}%</span></span>`;
+ rchip.innerHTML = `<span class="dot" style="background:${ramColor(ram.ram_pct)}"></span>`
+ + `<span class="cnt">🧠 <span style="color:${ramColor(ram.ram_pct)};font-weight:700">${ram.ram_pct}%</span> <span style="color:var(--dim);font-weight:600">⚙ ${ram.cpu_pct}%</span></span>`;
rchip.onclick = () => toggle('ram');
bar.appendChild(rchip);
const snbtn = document.createElement('button');
diff --git a/server.js b/server.js
index c67ab1c..f81100b 100755
--- a/server.js
+++ b/server.js
@@ -137,20 +137,29 @@ async function sniperOn() {
}
// Zero-dep stats: system RAM%/CPU% from `top`, top hogs from `ps` (no psutil).
async function getRam() {
- const [{ stdout: topOut }, { stdout: memBytes }, { stdout: psOut }] = await Promise.all([
+ const [{ stdout: topOut }, { stdout: memBytes }, { stdout: psOut }, mp] = await Promise.all([
run('top', ['-l', '1', '-n', '0']),
run('sysctl', ['-n', 'hw.memsize']),
run('ps', ['-axo', 'pid=,rss=,comm=']),
+ run('memory_pressure', []).then(r => r.stdout).catch(() => ''), // resilient: never rejects the batch
]);
// CPU: "CPU usage: 4.1% user, 5.2% sys, 90.6% idle" -> used = 100 - idle.
let cpu = 0;
const idle = /CPU usage:.*?([\d.]+)%\s*idle/i.exec(topOut);
if (idle) cpu = Math.max(0, Math.min(100, 100 - parseFloat(idle[1])));
- // RAM: "PhysMem: 21G used (2.0G wired), 11G unused." -> used/(used+unused).
- const toGB = (n, u) => parseFloat(n) * ({ K: 1 / 1048576, M: 1 / 1024, G: 1, T: 1024 }[u] || 1);
+ // RAM: use macOS memory-pressure (Activity Monitor's real strain signal) so the chip reflects
+ // genuine pressure, not cache. "used" = 100 - free%. macOS keeps RAM ~90% full with reclaimable
+ // file cache, so the old `top` PhysMem used/(used+unused) read ~96% and cried wolf (Steve 2026-09-23).
let ramPct = 0;
- const pm = /PhysMem:\s*([\d.]+)([KMGT])\s*used.*?([\d.]+)([KMGT])\s*unused/i.exec(topOut);
- if (pm) { const used = toGB(pm[1], pm[2]), unused = toGB(pm[3], pm[4]); if (used + unused) ramPct = used / (used + unused) * 100; }
+ const freeM = /free percentage:\s*([\d.]+)%/i.exec(mp);
+ if (freeM) {
+ ramPct = Math.max(0, Math.min(100, 100 - parseFloat(freeM[1])));
+ } else {
+ // fallback if memory_pressure is unavailable: the legacy top PhysMem used/(used+unused)
+ const toGB = (n, u) => parseFloat(n) * ({ K: 1 / 1048576, M: 1 / 1024, G: 1, T: 1024 }[u] || 1);
+ const pm = /PhysMem:\s*([\d.]+)([KMGT])\s*used.*?([\d.]+)([KMGT])\s*unused/i.exec(topOut);
+ if (pm) { const used = toGB(pm[1], pm[2]), unused = toGB(pm[3], pm[4]); if (used + unused) ramPct = used / (used + unused) * 100; }
+ }
const totalBytes = parseInt(memBytes, 10) || 0;
const hogs = psOut.trim().split('\n').map(l => {
const m = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(l);
← fa809cb dotbar: visible dotted resize grip + one-click Compact/Full
·
back to Desktop Dotbar
·
dotbar: merge ticket-board summary (BLOCKED/DOING/OPEN count 4ad7b46 →