← Commit history

0.5.53: widget helper keeps stale numbers dimmed while a token refreshes

John Lauer ·d48413ab4c · ·parent 35921d0
3 files changed +22−10
bin/hydrogen-widget-lib.py+18−6
@@ -23,6 +23,7 @@ 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}+#w.stale{opacity:.45} @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}@@ -47,7 +48,9 @@ html,body{margin:0;height:100%%;background:transparent;color:#e6ebef;font-family <script> const R=%(refresh_ms)d; function bar(l,p,sev){const c=(sev==='critical'||p>=90)?'hot':(sev==='warning'||p>=70)?'warn':'';return `<span class="row"><span class="lbl">${l}</span><span class="bar ${c}"><i style="width:${Math.max(0,Math.min(100,p))}%%"></i></span><span class="v">${Math.round(p)}%%</span></span>`;}-function render(d){document.getElementById('w').innerHTML=%(render_js)s;postWidth();}+/* d.stale = the last good data while the provider is unreachable or the token is between refreshes:+   drawn dimmed, the reason in the tooltip, never an error where numbers were. */+function render(d0){const d=d0&&d0.stale?d0.stale:d0;const el=document.getElementById('w');el.classList.toggle('stale',!!(d0&&d0.stale));el.title=(d0&&d0.stale)?String(d0.error||''):'';el.innerHTML=%(render_js)s;postWidth();} /* 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    out at max-content, so the slot's current width never feeds back into the answer (rows stretch to@@ -71,19 +74,28 @@ def serve(title, data_fn, render_js, refresh_ms=60000, mark=None):     port = int(os.environ.get('HYDROGEN_WIDGET_PORT') or (sys.argv[sys.argv.index('--port')+1] if '--port' in sys.argv else 8930))     # One upstream call per interval at most, however often the page asks; on an upstream     # error the interval doubles up to 15 minutes and returns to normal on the next success.-    cache = {'t': 0, 'v': {'error': 'starting'}, 'wait': refresh_ms/1000}+    cache = {'t': 0, 'v': {'error': 'starting'}, 'wait': refresh_ms/1000, 'good': None}     lock = threading.Lock()     def data():         with lock:             if time.time() - cache['t'] >= cache['wait'] - 1:                 try:                     cache['v'] = data_fn(); cache['wait'] = refresh_ms/1000+                    if not cache['v'].get('error'): cache['good'] = cache['v']                 except Exception as e:-                    # A 401/403 from the provider is an expired sign-in, so say that instead of-                    # printing the HTTP error (ConfRoomROG 2026-09-14: "HTTP Error 401: Unauthorized").+                    # A 401/403 is almost always an EXPIRED access token, not a missing sign-in:+                    # Claude Code and Codex refresh their own tokens the next time they run, and+                    # the widget re-reads the file. Never tell a signed-in user to sign in again+                    # (AdomLapper 2026-09-14: "sign in to Claude again" while John was chatting).+                    # Say what is true, keep the last good numbers on screen (dimmed), and retry+                    # every minute instead of backing off; other errors still back off.                     code = getattr(e, 'code', None)-                    msg = ('sign in to %s again' % title.replace(' usage', '')) if code in (401, 403) else str(e)[:120]-                    cache['v'] = {'error': msg}; cache['wait'] = min(900, max(refresh_ms/1000, cache['wait']) * 2)+                    if code in (401, 403):+                        msg = '%s token expired, waiting for %s to refresh it' % (title.replace(' usage', ''), 'Claude Code' if 'Claude' in title else 'Codex')+                        cache['wait'] = 60+                    else:+                        msg = str(e)[:120]; cache['wait'] = min(900, max(refresh_ms/1000, cache['wait']) * 2)+                    cache['v'] = {'error': msg, 'stale': cache['good']} if cache['good'] else {'error': msg}                 cache['t'] = time.time()             return cache['v']     class H(BaseHTTPRequestHandler):
package.json+2−2
@@ -4,7 +4,7 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.5.52",+  "version": "0.5.53",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",   "tags": [@@ -33,4 +33,4 @@     "hydrogen editor configuration",     "where do hydrogen skills come from"   ]-}\ No newline at end of file+}
page.json+2−2
@@ -4,7 +4,7 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.5.52",+  "version": "0.5.53",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",   "tags": [@@ -49,4 +49,4 @@     "hydrogen editor configuration",     "where do hydrogen skills come from"   ]-}\ No newline at end of file+}