← back to Loops Dashboard
fix: cache + background refresh — 21s → <100ms page load
36c10b11b8045c4e5a71494b89f68ca64bb499fa · 2026-05-08 07:34:54 -0700 · Steve Abrams
Files touched
Diff
commit 36c10b11b8045c4e5a71494b89f68ca64bb499fa
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri May 8 07:34:54 2026 -0700
fix: cache + background refresh — 21s → <100ms page load
---
server.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 52 insertions(+), 6 deletions(-)
diff --git a/server.js b/server.js
index 120653d..aa9cd89 100644
--- a/server.js
+++ b/server.js
@@ -344,20 +344,66 @@ function renderPage(data) {
</body></html>`;
}
+// --------------------------------- Cache + background refresh ---------------------------------
+// Each source has a TTL. Page renders read cache instantly (snappy). A background
+// loop refreshes stale entries every REFRESH_MS so the page never blocks on slow ops.
+const REFRESH_MS = 30_000;
+const cache = {
+ idea: { value: null, ts: 0, ttl: 5_000 }, // cheap (file reads), short TTL
+ codex3: { value: null, ts: 0, ttl: 30_000 }, // git show calls, 30s
+ morning: { value: null, ts: 0, ttl: 60_000 }, // 6 scanners on the other end, 1 min
+ launchd: { value: null, ts: 0, ttl: 15_000 }, // launchctl list, fast
+ pm2: { value: null, ts: 0, ttl: 30_000 } // pm2 jlist, slow, 30s
+};
+
+async function refresh(key, fn) {
+ try {
+ const v = await fn();
+ cache[key].value = v;
+ cache[key].ts = Date.now();
+ } catch (e) {
+ console.error(`[cache:${key}] refresh error:`, e.message);
+ }
+}
+
+async function refreshAll() {
+ await Promise.all([
+ refresh('idea', async () => getIdeaLoopState()),
+ refresh('codex3', async () => getCodex3WayState()),
+ refresh('morning', async () => await getMorningReviewState()),
+ refresh('launchd', async () => getLaunchdState()),
+ refresh('pm2', async () => getPm2State())
+ ]);
+}
+
+// Read cache (refresh in background if stale; never block the read).
+function readCache(key, fallback) {
+ const c = cache[key];
+ if (!c.value) return fallback;
+ return c.value;
+}
+
+// Kick off initial refresh + interval
+refreshAll().catch(() => {});
+setInterval(() => { refreshAll().catch(() => {}); }, REFRESH_MS);
+
// --------------------------------- Server ---------------------------------
const server = http.createServer(async (req, res) => {
if (req.url === '/healthz') {
res.writeHead(200, { 'Content-Type': 'application/json' });
- return res.end(JSON.stringify({ ok: true, ts: new Date().toISOString() }));
+ return res.end(JSON.stringify({
+ ok: true, ts: new Date().toISOString(),
+ cache_ages_s: Object.fromEntries(Object.entries(cache).map(([k, v]) => [k, v.ts ? Math.round((Date.now() - v.ts) / 1000) : -1]))
+ }));
}
try {
- const idea = getIdeaLoopState();
- const codex3 = getCodex3WayState();
- const morning = await getMorningReviewState();
- const launchd = getLaunchdState();
- const pm2 = getPm2State();
+ const idea = readCache('idea', { accepted: 0, rejected: 0, decisions: 0, top: [], last_tick_age: '—' });
+ const codex3 = readCache('codex3', []);
+ const morning = readCache('morning', { total: 0, by_source: {} });
+ const launchd = readCache('launchd', { total: 0, problems: 0, problem_jobs: [], ai_loop_jobs: [] });
+ const pm2 = readCache('pm2', { total: 0, online: 0, errored: 0, ai_loop: [] });
if (req.url === '/api/state') {
res.writeHead(200, { 'Content-Type': 'application/json' });
← 5ba48a6 initial scaffold: loops-dashboard at :9930 aggregating idea-
·
back to Loops Dashboard
·
gitignore: protect .env* from accidental commits per MEMORY f4cb25a →