[object Object]

← back to Desktop Dotbar

desktop-dotbar: collapsed PARKED chip/panel from the durable registry

b102cca8d022089a0668f41c5e0abbd9ca255f86 · 2026-09-20 11:29:57 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QZehHfR2cgtmFvznNavrn5

Files touched

Diff

commit b102cca8d022089a0668f41c5e0abbd9ca255f86
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Sep 20 11:29:57 2026 -0700

    desktop-dotbar: collapsed PARKED chip/panel from the durable registry
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01QZehHfR2cgtmFvznNavrn5
---
 public/index.html | 34 +++++++++++++++++++++++++++++++++-
 server.js         | 17 +++++++++++++++--
 2 files changed, 48 insertions(+), 3 deletions(-)

diff --git a/public/index.html b/public/index.html
index 0ef6b05..5d8f511 100644
--- a/public/index.html
+++ b/public/index.html
@@ -135,6 +135,18 @@ function renderBar(){
     chip.onclick = () => toggle(g.color);
     bar.appendChild(chip);
   }
+  // Durable PARKED chip (TK-11946) — collapsed badge, distinct from live pink; opens a
+  // panel listing the registry (parked tabs + parked tickets). Hidden when the registry is empty.
+  if (data.parked && data.parked.count){
+    const pchip = document.createElement('div');
+    pchip.className = 'chip' + (openColor==='parked' ? ' active':'');
+    const showName = (openColor==='parked') || cfg.orientation !== 'top';
+    pchip.innerHTML = `<span class="dot" style="background:#ff8fc8"></span>`
+      + `<span class="cnt">${data.parked.count}</span>`
+      + (showName ? `<span class="nm">parked</span>` : '');
+    pchip.onclick = () => toggle('parked');
+    bar.appendChild(pchip);
+  }
   const sp = document.createElement('div'); sp.id='spacer'; bar.appendChild(sp);
   const meta = document.createElement('div'); meta.id='meta';
   const nextO = cfg.orientation==='top'?'left':cfg.orientation==='left'?'right':'top';
@@ -150,6 +162,23 @@ function renderBar(){
 function renderPanel(){
   const panel = document.getElementById('panel');
   if (!openColor){ panel.className=''; panel.innerHTML=''; return; }
+  // Durable PARKED panel (TK-11946): registry entries, not a live colour group.
+  if (openColor==='parked'){
+    panel.className = 'open';
+    const items = (data.parked && data.parked.items) || [];
+    if (!items.length){ panel.innerHTML = `<div class="empty">No parked items.</div>`; return; }
+    let html = `<div class="phead">🩷 parked (durably held) — ${items.length}</div>`;
+    for (const s of items){
+      html += `<div class="row">`
+        + `<span class="rdot" style="background:#ff8fc8"></span>`
+        + `<span class="tk">${s.ticket || s.id || '—'}</span>`
+        + `<span class="doing" title="${(s.doing||'').replace(/"/g,'&quot;')}">${s.doing || ('('+s.kind+')')}</span>`
+        + `<span class="tty">${s.tty || s.kind}</span>`
+        + (s.tty ? `<button class="open" onclick='reveal(${JSON.stringify(s.tty)})'>open terminal ↗</button>` : '')
+        + `</div>`;
+    }
+    panel.innerHTML = html; return;
+  }
   const g = data.groups.find(x=>x.color===openColor);
   panel.className = 'open';
   if (!g || !g.sessions.length){ panel.innerHTML = `<div class="empty">No ${openColor} sessions.</div>`; return; }
@@ -199,7 +228,10 @@ async function tick(){
   const d = await fetchDots();
   if (!d) return;
   data = d;
-  if (openColor && !data.groups.some(g=>g.color===openColor && g.count>0)) {
+  const openStillHasItems = openColor==='parked'
+    ? !!(data.parked && data.parked.count)
+    : data.groups.some(g=>g.color===openColor && g.count>0);
+  if (openColor && !openStillHasItems) {
     if (++_miss >= 2) { openColor=null; resize(false); _miss=0; }
   } else { _miss = 0; }
   renderBar(); renderPanel();
diff --git a/server.js b/server.js
index f4dac89..51345fb 100755
--- a/server.js
+++ b/server.js
@@ -11,6 +11,7 @@ const path = require('path');
 
 const ALLCOLORDOTS = `${process.env.HOME}/.claude/skills/allcolordots/allcolordots.sh`;
 const ROUTER = `${process.env.HOME}/.claude/skills/dot-screen-router/router.sh`;
+const PARKED_MJS = `${process.env.HOME}/.claude/skills/parked/parked.mjs`;  // durable PARKED registry (TK-11946)
 
 // Urgency order (matches allcolordots): needs-Steve first, working/parked last.
 const ORDER = ['lightblue', 'orange', 'purple', 'yellow', 'green', 'pink', 'none'];
@@ -49,7 +50,14 @@ async function getDots() {
   const { stdout } = await run('bash', [ALLCOLORDOTS, '--json'], 120000);
   let rows = [];
   try { rows = JSON.parse(stdout || '[]'); } catch { rows = []; }
-  rows = rows.filter(r => r && r.live); // only live sessions count as "running"
+  // Durable PARKED group straight from the registry (both tab + ticket kinds),
+  // collapsed and distinct from the transient live-pink dots.
+  const { stdout: pj } = await run(process.execPath, [PARKED_MJS, 'list-parked', '--json'], 8000);
+  let parked = [];
+  try { parked = JSON.parse(pj || '[]'); } catch { parked = []; }
+  // A durably-parked live tab peels OUT of the active colour groups into the PARKED
+  // section, so the active dots stay uncluttered (allcolordots --json carries `parked`).
+  rows = rows.filter(r => r && r.live && !r.parked);
   const groups = {};
   for (const key of ORDER) groups[key] = [];
   for (const r of rows) {
@@ -64,7 +72,12 @@ async function getDots() {
   const out = ORDER
     .map(color => ({ color, ...META[color], count: groups[color].length, sessions: groups[color] }))
     .filter(g => g.count > 0 || g.color === 'green'); // always show green; hide empty others
-  return { updated: Date.now(), total: rows.length, groups: out };
+  const parkedItems = parked.map(e => ({
+    kind: e.kind, id: e.id, ticket: e.kind === 'ticket' ? e.id : ticketOf(e.label),
+    tty: e.tty || (e.kind === 'tab' ? e.id : ''), doing: cleanLabel(e.label), parked_at: e.parked_at,
+  }));
+  return { updated: Date.now(), total: rows.length, groups: out,
+    parked: { count: parkedItems.length, items: parkedItems } };
 }
 
 // Bring the iTerm2 session whose tty matches to the front.

← d88f4e1 dotbar: horizontal contract/expand for the top bar (right-en  ·  back to Desktop Dotbar  ·  Add RAM Sniper to the nav bar: live RAM%/CPU% chip, ON/OFF d 34abb18 →