diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-22 01:23:47 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-22 01:23:47 +0200 |
| commit | 8dd236e7a03b3cfe3cfb4a0d77b62ce112e4d418 (patch) | |
| tree | 807a8736b0c711b8d7d9a3b667df67d78165ca5d /downloads/dashboard.html | |
| parent | 70b9a2f8cc2b249362ca4893fdbb6703c0ea7b8a (diff) | |
fix(newos4): dashboard shows the real world model and stops dying after 10 s (v4.6.51)
Two defects, both verified live before/after:
1. The World Model panel always said "Not synthesized yet". The page had a
renderer for data.worldModel and _collectDashboardData() never set it -- the
model was right there in WorldModelModule._model. Now collected and mapped to
the renderer's exact keys (model has modules.loaded/skills.count/memory.nodes,
the page reads modules/skills/memoryNodes), plus changesSinceLastSnapshot and
the recent-error sample, with an honest "not synthesized" fallback.
2. The page discarded any payload older than 10 s and its own 5 s auto-refresh
then showed "Cannot connect to newOS4". Stale payloads are now rendered with
their age ("Data as of 1:20:14 AM - 3m ago"), labelled amber past 5 min.
Also: the Refresh button wrote a localStorage key nobody read -> the terminal now
listens for it and re-collects (page says "No terminal tab answered" when no
terminal tab is open); storage events repaint instantly; the dead /api/dashboard
fetch (404 on all three paths, 5 s stall) is gone; `dashboard --watch` / `--stop`
re-collects every 5 s; header says the data is this browser's instance.
Verified v4.6.51: panel rows "Synthesized 34s ago / v4.6.51 / 46-22 / 136-3 /
1 nodes 0 edges / OK / none recorded"; 130 s-old payload still renders with
"- 3m ago" and no banner (previously "Cannot connect"); storage event moves the
footer 13s -> 0s; watch timer advances the payload, --stop clears it; mobile
360/390 px all OK; 0 console errors.
Diffstat (limited to 'downloads/dashboard.html')
| -rw-r--r-- | downloads/dashboard.html | 126 |
1 files changed, 64 insertions, 62 deletions
diff --git a/downloads/dashboard.html b/downloads/dashboard.html index d4275f2..799bdbb 100644 --- a/downloads/dashboard.html +++ b/downloads/dashboard.html @@ -252,6 +252,9 @@ <header> <div> <h1>⬡ newOS4 Dashboard</h1> + <div class="sub" style="color:var(--text-dim);font-size:0.85rem"> + Live view of this browser's newOS4 instance · world model, agents, tokens, latency + </div> <div class="meta">Live system telemetry · <span id="version-display">v?</span></div> </div> <div class="header-controls"> @@ -353,7 +356,8 @@ </div> <div class="last-updated"> - Last updated: <span id="last-updated-time">--</span> · Auto-refresh every 5s + Data as of <span id="last-updated-time">--</span> · updates when the terminal re-collects + (<code>dashboard</code> or <code>dashboard --watch</code>) </div> </div> @@ -395,18 +399,17 @@ } } + // 2026-10-22: a 10 s cutoff threw away perfectly good data and the page's own auto-refresh + // then showed "Cannot connect". Keep whatever exists and let the UI label its age. function fetchDashboardData() { - // Try to get data from localStorage (set by the AI tool or terminal command) try { const raw = localStorage.getItem(DASHBOARD_KEY); - if (raw) { - const data = JSON.parse(raw); - if (data && data.timestamp && (Date.now() - data.timestamp < 10000)) { - return data; // fresh enough - } - } - } catch (e) {} - return null; + if (!raw) return null; + const data = JSON.parse(raw); + if (!data || !data.timestamp) return null; + data._ageMs = Math.max(0, Date.now() - data.timestamp); + return data; + } catch (e) { return null; } } function renderDashboard(data) { @@ -418,6 +421,10 @@ hideError(); updateConnectionStatus(true); + + const ageSec = Math.round((data._ageMs || 0) / 1000); + const ageEl = document.getElementById('last-updated-time'); + if (ageEl) ageEl.textContent = new Date(data.timestamp).toLocaleTimeString() + ' · ' + (ageSec < 90 ? ageSec + 's ago' : Math.round(ageSec / 60) + 'm ago'); lastData = data; // Version @@ -536,26 +543,34 @@ // ── World Model ── const wmDiv = document.getElementById('world-model-metrics'); const wmData = data.worldModel; - if (wmData) { - const synTime = wmData.synthesized ? new Date(wmData.synthesized).toLocaleTimeString() : '?'; + if (wmData && !wmData.missing) { + const ageMs = wmData.synthesized ? (Date.now() - new Date(wmData.synthesized).getTime()) : null; + const ageStr = ageMs === null ? '?' : (ageMs < 90000 ? Math.round(ageMs / 1000) + 's ago' : Math.round(ageMs / 60000) + 'm ago'); + const stale = ageMs !== null && ageMs > 5 * 60 * 1000; let wmHtml = ''; + wmHtml += '<div class="metric-row"><span>Synthesized</span><span' + (stale ? ' style="color:var(--yellow,#e0b341)"' : '') + '>' + ageStr + (stale ? ' (stale)' : '') + '</span></div>'; wmHtml += '<div class="metric-row"><span>Version</span><span>' + (wmData.version || '?') + '</span></div>'; - wmHtml += '<div class="metric-row"><span>Last synthesized</span><span>' + synTime + '</span></div>'; - wmHtml += '<div class="metric-row"><span>Modules</span><span>' + (wmData.modules || '?') + '</span></div>'; - wmHtml += '<div class="metric-row"><span>Skills</span><span>' + (wmData.skills || 0) + '</span></div>'; - wmHtml += '<div class="metric-row"><span>Files</span><span>' + (wmData.files || 0) + '</span></div>'; - wmHtml += '<div class="metric-row"><span>Agents</span><span>' + (wmData.agents || 0) + '</span></div>'; - wmHtml += '<div class="metric-row"><span>Memory graph</span><span>' + (wmData.memoryNodes || 0) + ' nodes, ' + (wmData.memoryEdges || 0) + ' edges</span></div>'; + wmHtml += '<div class="metric-row"><span>Modules / skills</span><span>' + (wmData.modules ?? '?') + ' / ' + (wmData.skills ?? '?') + '</span></div>'; + wmHtml += '<div class="metric-row"><span>Files / agents</span><span>' + (wmData.files ?? '?') + ' / ' + (wmData.agents ?? '?') + '</span></div>'; + wmHtml += '<div class="metric-row"><span>Memory graph</span><span>' + (wmData.memoryNodes ?? 0) + ' nodes · ' + (wmData.memoryEdges ?? 0) + ' edges</span></div>'; wmHtml += '<div class="metric-row"><span>Health</span><span>' + (wmData.health || '?') + '</span></div>'; - if (wmData.activeProjects && wmData.activeProjects.length > 0) { - wmHtml += '<div class="metric-row"><span>Active projects</span><span>' + wmData.activeProjects.join(', ') + '</span></div>'; + if (wmData.changes && wmData.changes.length) { + wmHtml += '<div class="metric-row"><span>Since last snapshot</span><span>' + wmData.changes.join('; ') + '</span></div>'; } - if (wmData.recent && wmData.recent.length > 0) { - wmHtml += '<div class="metric-row"><span>Recent changes</span><span>' + wmData.recent.join('; ') + '</span></div>'; + if (wmData.errors && wmData.errors.length) { + wmHtml += '<div class="metric-row"><span>Recent errors</span><span>' + wmData.errors.join(' | ') + '</span></div>'; + } else { + wmHtml += '<div class="metric-row"><span>Recent errors</span><span>✅ none recorded</span></div>'; } + if (wmData.recent && wmData.recent.length) { + wmHtml += '<div class="metric-row"><span>Current</span><span>' + wmData.recent.join('; ') + '</span></div>'; + } + wmHtml += '<div class="metric-row" style="color:var(--text-dim)"><span>Ask it anything</span><span>world-model-ask <question></span></div>'; wmDiv.innerHTML = wmHtml; + } else if (wmData && wmData.error) { + wmDiv.innerHTML = '<div class="metric-row" style="color:var(--red,#e5534b)">World model read failed: ' + wmData.error + '</div>'; } else { - wmDiv.innerHTML = '<div class="metric-row" style="color:var(--text-dim)">World model not synthesized — run world-model-refresh in terminal</div>'; + wmDiv.innerHTML = '<div class="metric-row" style="color:var(--text-dim)">World model not synthesized — run <code>world-model-refresh</code> in the terminal</div>'; } // ── System Health ── @@ -570,51 +585,36 @@ healthDiv.innerHTML = healthHtml; // Timestamp - document.getElementById('last-updated-time').textContent = new Date().toLocaleTimeString(); + // Footer age is set at the top of renderDashboard from data.timestamp (payload age); + // the old line here wrote the wall clock and silently clobbered it. } function refreshNow() { - // Signal the terminal to update dashboard data - try { - localStorage.setItem(DASHBOARD_KEY + '_request', Date.now().toString()); - } catch (e) {} - // Try reading fresh data + try { localStorage.setItem(DASHBOARD_KEY + '_request', Date.now().toString()); } catch (e) {} + window._dashRefreshAskedAt = Date.now(); const data = fetchDashboardData(); - if (data) { - renderDashboard(data); - } else { - // If no data, try to fetch via the newOS4 API - fetchDashboardFromAPI(); - } + if (data) renderDashboard(data); + setTimeout(function () { + const d = fetchDashboardData(); + const fresh = d && d.timestamp > (window._dashRefreshAskedAt || 0); + if (!fresh) showError('No terminal tab answered — run dashboard (or dashboard --watch) in newOS4, then press ⟳ again'); + else hideError(); + }, 1500); } - async function fetchDashboardFromAPI() { - showLoading(); + // 2026-10-22: the terminal writes this key on every collect; repaint immediately instead of + // waiting for the 5 s poll (and never go stale while the terminal tab is open). + window.addEventListener('storage', function (ev) { + if (ev.key !== DASHBOARD_KEY || !ev.newValue) return; try { - // Try the proxy API endpoint - const resp = await fetch('/api/dashboard', { signal: AbortSignal.timeout(5000) }); - if (resp.ok) { - const data = await resp.json(); - localStorage.setItem(DASHBOARD_KEY, JSON.stringify({...data, timestamp: Date.now()})); - renderDashboard(data); - showContent(); - return; - } - } catch (e) { - // API not available — try localStorage fallback - } - - // Fallback: check localStorage - const data = fetchDashboardData(); - if (data) { - renderDashboard(data); + const d = JSON.parse(ev.newValue); + d._ageMs = 0; + renderDashboard(d); showContent(); - } else { - showError('Cannot connect to newOS4. Open the terminal and run: dashboard'); - updateConnectionStatus(false); - showContent(); - } - } + hideError(); + updateConnectionStatus(true); + } catch (e) {} + }); // Auto-refresh loop function startAutoRefresh() { @@ -625,13 +625,15 @@ }, REFRESH_INTERVAL); } - // Init + // Init: render whatever the terminal left behind, otherwise say how to produce data. const initialData = fetchDashboardData(); if (initialData) { renderDashboard(initialData); showContent(); } else { - fetchDashboardFromAPI(); + showError('No dashboard data yet — run dashboard in the newOS4 terminal'); + updateConnectionStatus(false); + showContent(); } startAutoRefresh(); |