← Commit history
VERSION+1−1
@@ -1 +1 @@-1.1.90+1.1.91
bin/adom-vscode
⋯ 1 unchanged line ⋯
bin/hydrogen-widget-claude-usage

  
bin/hydrogen-widget-codex-usage

  
bin/hydrogen-widget-lib.py+25−5
@@ -36,6 +36,10 @@ html,body{margin:0;height:100%%;background:transparent;color:#e6ebef;font-family .v{font-variant-numeric:tabular-nums;text-align:right} .big{font-weight:600;font-variant-numeric:tabular-nums} .err{color:#f0a060}+/* An error is a button: a click checks again now (John 2026-09-17: "make this clickable so i can+   manually refresh it after i login", and "i can't read this text its so small"). */+#wrap.act{cursor:pointer}#wrap.act:hover .err{text-decoration:underline;text-underline-offset:2px}+.checking{opacity:.7} /* Stale (last numbers while the provider is between answers): a light fade only, so the colours    read the same as a fresh neighbour (John 2026-09-15: "the claude one has washed out colors"). */ #w.stale{opacity:.78}@@ -50,7 +54,7 @@ html,body{margin:0;height:100%%;background:transparent;color:#e6ebef;font-family @media (max-height:33px){   .w{font-size:clamp(7px,37vh,12px);line-height:1.2}   .lbl{font-size:clamp(6px,30vh,9px)}.bar{width:clamp(24px,130vh,40px);height:clamp(4px,20vh,6px)}.v{min-width:2.4em}-  .big{font-size:clamp(9px,47vh,14px)}.err{font-size:clamp(7px,33vh,10px)}+  .big{font-size:clamp(9px,47vh,14px)}.err{font-size:clamp(9px,52vh,12px)}   .mark{width:clamp(10px,55vh,17px);height:clamp(10px,55vh,17px)}   .cell{flex-direction:row;align-items:baseline;gap:3px}   .pct{font-size:clamp(9px,66vh,14px)}.cell .lbl{font-size:clamp(6px,32vh,8px)}@@ -68,7 +72,7 @@ html,body{margin:0;height:100%%;background:transparent;color:#e6ebef;font-family   .rows.hz{flex-direction:row;align-items:center;gap:6px}   .row{display:grid;grid-template-columns:auto minmax(18px,1fr) auto;align-items:center;gap:3px;min-width:0}   .lbl{font-size:6.5px}.row .lbl{max-width:4.2em;overflow:hidden;text-overflow:ellipsis}.bar{width:auto;height:4px}.v{font-size:8px;min-width:0}-  .big{font-size:14px}.err{font-size:8px;white-space:normal;line-height:1.1;max-width:96px}+  .big{font-size:14px}.err{font-size:11.5px;white-space:normal;line-height:1.15;max-width:150px}   /* Big numbers fill the 36 px the slot gives: 22 px digits over a 7 px label. */   .cell .lbl{font-size:7px}.pct{font-size:22px}   /* One percentage size in every view (John 2026-09-15: Claude's all-numbers view sat at 11.6 px@@ -113,8 +117,8 @@ function render(d0){const d=d0&&d0.stale?d0.stale:d0;const el=document.getElemen   if(d&&Array.isArray(d.items)&&d.items.length){const vs=viewsFor(d.items);let k=vs.findIndex(v=>v.id===curView);if(k<0){k=0;curView='rows';}     el.classList.toggle('hz',curView!=='rows');el.innerHTML=renderItems(d.items,curView);     tip=(tip?tip+'. ':'')+'%(title)s: '+vs[k].name+'. Click for the next view ('+(k+1)+' of '+vs.length+').';}-  else{el.classList.remove('hz');el.innerHTML=%(render_js)s;}-  wrap.title=tip;postWidth();}+  else{el.classList.remove('hz');el.innerHTML=%(render_js)s;tip=(d0&&d0.error?String(d0.error)+'. ':'')+'Click to check again now.';}+  wrap.classList.toggle('act',!!(d0&&d0.error));wrap.title=tip;postWidth();} /* The click's own re-render must carry the NEW view: window.__last is the last poll's copy, whose    view field still names the old one, and render() adopts the server's view over the local one.    Without this line a click changed the server and nothing on screen until the next poll, three@@ -123,7 +127,16 @@ function setView(id){curView=id;try{localStorage.setItem('hw.view',id);}catch(e)   if(window.__last)window.__last.view=id;   fetch('/view',{method:'POST',body:id}).then(r=>{if(!r.ok)throw new Error('http '+r.status);}).catch(()=>{setTimeout(()=>fetch('/view',{method:'POST',body:id}).catch(()=>{}),3000);});   if(window.__last)render(window.__last);}-document.getElementById('wrap').addEventListener('click',()=>{const d0=window.__last;const d=d0&&d0.stale?d0.stale:d0;if(!d||!Array.isArray(d.items)||!d.items.length)return;+/* CHECK NOW: POST /refresh makes the server ask the provider (or re-read the sign-in file) right away+   instead of at its next scheduled check, and answers with the fresh data. One call per click. */+let refreshing=false;+async function refreshNow(showChecking){if(refreshing)return;refreshing=true;+  if(showChecking){const el=document.getElementById('w');el.classList.remove('hz');el.innerHTML='<span class="err checking">checking…</span>';postWidth();}+  try{const r=await fetch('/refresh',{method:'POST',cache:'no-store'});if(!r.ok)throw new Error('http '+r.status);const d=await r.json();window.__last=d;render(d);}+  catch(e){tick();}finally{refreshing=false;}}+document.getElementById('wrap').addEventListener('click',()=>{const d0=window.__last;const d=d0&&d0.stale?d0.stale:d0;+  if(d0&&d0.error){if(!d||!Array.isArray(d.items)||!d.items.length){refreshNow(true);return;}refreshNow(false);}+  if(!d||!Array.isArray(d.items)||!d.items.length)return;   const vs=viewsFor(d.items);const k=vs.findIndex(v=>v.id===curView);setView(vs[(k+1)%%vs.length].id);}); /* THE WIDGET DECIDES ITS WIDTH: ask the host for the intrinsic width of what is shown in the    current layout (tall rows are narrow, the compact row is wide). Measured on a hidden clone laid@@ -222,6 +235,11 @@ def serve(title, data_fn, render_js, refresh_ms=60000, mark=None, align_ms=0):             out = dict(cache['v']); out['view'] = cache['view']             if out.get('error'): out['retry_in'] = max(0, int(cache['wait'] - (time.time() - cache['t'])))             return out+    def refresh_now():+        # A click on the widget: make the next data() call due now, whatever the schedule says.+        with lock:+            cache['t'] = 0; cache['expired_mtime'] = None+        return data()     def set_view(v):         with lock:             cache['view'] = v@@ -237,6 +255,8 @@ def serve(title, data_fn, render_js, refresh_ms=60000, mark=None, align_ms=0):                 v = self.rfile.read(n).decode(errors='replace').strip()[:40] if n else ''                 if v: set_view(v)                 self._send(200, 'application/json', json.dumps({'ok': True, 'view': cache['view']}).encode())+            elif self.path.startswith('/refresh'):+                self._send(200, 'application/json', json.dumps(refresh_now()).encode())             else: self._send(404, 'text/plain', b'not found')         def do_GET(self):             if self.path.startswith('/data.json'): self._send(200, 'application/json', json.dumps(data()).encode())
bin/hydrogen-widget-marks.py

  
install.sh

  
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-vscode",-  "version": "1.1.90",+  "version": "1.1.91",   "type": "app",   "description": "CLI + VS Code extension that lets AI assistants and the Hydrogen Desktop frontend drive VS Code from inside an Adom container: open files, live get/set of any setting/theme/font (no reload), native streamed exec, a frontend event queue, container identity (hd/hw), api-key status/inject, extension query/update, modes, paste screenshots, and any VS Code command via a local HTTP API on port 8821 (with port-file fallback discovery).",   "license": "PROPRIETARY",
page.json+2−2
@@ -4,7 +4,7 @@   "slug": "adom-vscode",   "title": "adom-vscode",   "brief": "Let Claude, Codex and Kimi drive VS Code inside your Adom container",-  "version": "1.1.90",+  "version": "1.1.91",   "license": "PROPRIETARY",   "visibility": "public",   "hero": {@@ -141,4 +141,4 @@     "adom-vscode-extensions",     "adom-vscode-modes"   ]-}\ No newline at end of file+}