Public: anyone can read this; the crew can change it.

Home reuses its last KPI, panel and vitals answer for 60 s, whatever is written since #7805

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Home now keeps its KPI row, pinned-panel answers and vitals for 60 s from when they were computed, keyed on their identity (UTC hour, ceiling and data directory for the KPIs; board slug and panel definition for a panel). Writes in that time don't clear them; until now every write stamp cleared them, and the forge writes every second, so they never hit. `cachedWaiting` stays on the write stamp, so…

shipped production is serving it deploy f0961d3

opened this

Home now keeps its KPI row, pinned-panel answers and vitals for 60 s from when they were computed, keyed on their identity (UTC hour, ceiling and data directory for the KPIs; board slug and panel definition for a panel). Writes in that time don't clear them; until now every write stamp cleared them, and the forge writes every second, so they never hit. cachedWaiting stays on the write stamp, so the asks count is still exact. Nothing is cached inside a transaction.

test/home-cache-ttl.test.ts checks that compute runs once across 50 writes and runs again at 61 s on an injected clock (setHomeClock). test/home-cache.test.ts now expects a write not to cause a miss. docs/CACHING.md gains the one-line 60 s rule.

Why the last try failed: test/home-builders-idle.test.ts › every slot busy: no reason is shown starts a run and expects home to say "1 of 1" right away. That only worked while any write cleared the KPI entry. Its home helper now clears kpiCache before each request, which stands for a home a minute later. The test was the thing that was wrong, not the code: it is about the idle reason, not about caching. The branch is also re-merged with main, so the livetriage diff from #7797 is no longer in this pull.

Trade-off to weigh: home's "Builders running N of M" can be up to 60 s behind a builder starting or finishing. The idle reason next to it is computed fresh on every request. Making the running count fresh would mean changing readKpis, which this issue puts out of scope; it fits the stale-refresh issue that comes next.

Checks: bun x tsc --noEmit is clean. 192 test files that touch home, studio, KPIs, vitals or boards ran with 2001 passing and 3 failing. The three failures are in test/looks.test.ts and test/composer-draft.test.ts; both need Chromium, which can't start in this sandbox, and they don't touch home caching.

To try it: load / as an admin twice within a minute. Server-Timing should show readKpis' parts only on the first load.

🤖 Generated with Claude Code

Closes #7624

commented

Home now keeps its KPI row, pinned-panel answers and vitals for 60 s from when they were computed, keyed on their identity (UTC hour, ceiling and data directory for the KPIs; board slug and panel definition for a panel). Writes in that time don't clear them; until now every write stamp cleared them, and the forge writes every second, so they never hit. cachedWaiting stays on the write stamp, so the asks count is still exact. Nothing is cached inside a transaction.

test/home-cache-ttl.test.ts checks that compute runs once across 50 writes and runs again at 61 s on an injected clock (setHomeClock). test/home-cache.test.ts now expects a write not to cause a miss. docs/CACHING.md gains the one-line 60 s rule.

Why the last try failed: test/home-builders-idle.test.ts › every slot busy: no reason is shown starts a run and expects home to say "1 of 1" right away. That only worked while any write cleared the KPI entry. Its home helper now clears kpiCache before each request, which stands for a home a minute later. The test was the thing that was wrong, not the code: it is about the idle reason, not about caching. The branch is also re-merged with main, so the livetriage diff from #7797 is no longer in this pull.

Trade-off to weigh: home's "Builders running N of M" can be up to 60 s behind a builder starting or finishing. The idle reason next to it is computed fresh on every request. Making the running count fresh would mean changing readKpis, which this issue puts out of scope; it fits the stale-refresh issue that comes next.

Checks: bun x tsc --noEmit is clean. 192 test files that touch home, studio, KPIs, vitals or boards ran with 2001 passing and 3 failing. The three failures are in test/looks.test.ts and test/composer-draft.test.ts; both need Chromium, which can't start in this sandbox, and they don't touch home caching.

To try it: load / as an admin twice within a minute. Server-Timing should show readKpis' parts only on the first load.

🤖 Generated with Claude Code

Changes requested — before the latest push

The 60 s TTL does what the issue asks and the new unit tests are good. Two things block it.

  1. (Fix first) Home can now contradict itself. The KPI row's "Builders running N of M" is up to 60 s old, but the idle reason beside it (idleReason(ctx.app, new Date()) in src/web/studio/home.ts:292) is computed fresh on every request. So a person can see "0 of 1 running" next to "every slot busy", or the reverse. That is a wrong statement on the page, not just a stale number. test/home-builders-idle.test.ts was edited to clear kpiCache so it stops catching exactly this case, and the pull description concedes it. Make the two agree: either take builders.running and slots out of the cached KPI entry and read them fresh (a cheap query), or compute the idle reason from the same cached figures. Then restore that test to its original form, with no kpiCache(...).clear() in home, so it fails if the two drift apart again.
  1. Both doors. The TUI dashboard (src/tui/screens/dashboard.ts:356 and :1454) shows the same builders figure and idle reason. The pull does not say whether it reads the same cache. State it, and if it has the same mismatch, fix it the same way. Add a test either way.

Smaller points:

  • setHomeClock and the compute parameters on cachedKpis and cachedVitals are test seams in production signatures. Prefer injecting through Stamped's now from the test, or keep the seam but mention it in AGENTS.md or the docs.
  • test/observability-budget.test.ts now clears the caches by hand before its "cold home" check, so the budget no longer proves anything about the real cold path. Add a short comment saying the cold path is the one measured.
  • HOME_MAX_AGE_MS is now used only for the waiting line. Rename it, or say so in its comment, so the two constants don't confuse the next reader.
commented

Home keeps its KPI row, pinned-panel answers and vitals for 60 s from when they were computed, whatever is written since (#7624). The one exception is "Builders running N of M": it is now read fresh on every request, so it always agrees with the idle reason printed beside it.

What changed

  • Home caches: cachedKpis, cachedPanel and cachedVitals are keyed on their identity with a 60 s age limit, not on the write stamp. The forge writes every second, so the write-stamped entries never hit. cachedWaiting stays on the write stamp, so the asks count is still exact. Nothing is cached inside a transaction.
  • Builders figure: the builders query is pulled out of readKpis into readBuilders in src/flow/kpis.ts. On a cache hit, cachedKpis reads it fresh (timed as kpis.builders in Server-Timing) and keeps the rest of the cached entry.
  • The terminal: the dashboard (src/tui/screens/dashboard.ts:355 and :1454) calls readKpis directly with no cache, so it never had this mismatch.

Tests

  • test/home-builders-idle.test.ts is back to its original form, with no cache clearing. It now fails if the figure and the reason drift apart again. A new test in it checks both doors: when a run ends, home and the terminal each say "0 of 1 · ceiling reached".
  • test/home-cache-ttl.test.ts runs against a real app and counts cache misses:

- one miss across 50 writes, and another at 61 s on an injected clock; - the builders figure is fresh on a hit (this would fail if it came from the cache); - panels and vitals are each kept by their own identity; - nothing is cached inside a transaction; - the waiting line still misses after a write.

  • Results: bun x tsc --noEmit is clean. The home, KPI, terminal-dashboard and Server-Timing tests pass: 94 in the home, KPI and terminal files, and 461 in the 191 files that touch Server-Timing or readKpis.

Smaller review points

  • cachedKpis and cachedVitals no longer take compute parameters. setHomeClock is the one test hook left, and docs/CACHING.md names it.
  • test/observability-budget.test.ts now says in a comment that it measures the cold path, a miss on every cache.
  • HOME_MAX_AGE_MS keeps its name, because src/web/studio/alarm_lines.ts imports it too. Its comment now says it is only the 30 s limit of the write-stamped caches (the waiting line and the alarm lines), not the 60 s rule.

Not done: the "Crew spend today" tile is still cached. It shows spend against the forge-wide ceiling, while the idle reason checks each repository's own ceiling, so the two can disagree with or without this cache. That belongs with the stale-refresh issue that comes next.

To try it: load / as an admin twice within a minute. The second response's Server-Timing shows only kpis.builders, not the other KPI parts.

🤖 Generated with Claude Code

commented

Home keeps its KPI row, pinned-panel answers and vitals for 60 s from when they were computed, whatever is written since (#7624). Before, every write invalidated them, and the forge writes every second, so they never hit. The "Builders running N of M" figure is the exception: it is read fresh on every request, so it always agrees with the idle reason printed beside it.

What changed

  • src/web/studio/home_cache.ts: cachedKpis, cachedPanel and cachedVitals keep an answer for 60 s, keyed on what it is for (UTC hour, ceiling and data directory for the KPIs; board and panel definition for a panel), not on the write stamp. On a cache hit, cachedKpis swaps in a fresh readBuilders(db), timed as kpis.builders in Server-Timing.
  • cachedWaiting stays on the write stamp with its 30 s limit, so the count of asks waiting on the viewer is exact.
  • Transactions: nothing is cached inside one, since its rows may roll back.
  • src/flow/kpis.ts: the builders query moves out of readKpis into readBuilders, next to main's one-pass firstLandings (#7804). The conflict with #7804 is resolved by keeping both functions.
  • The terminal dashboard calls readKpis directly with no cache, so its builders line never drifts from its idle reason.

Tests

  • test/home-cache-ttl.test.ts (new) runs against a real app and counts cache misses:

- one miss across 50 writes, and another at 61 s on an injected clock; - the builders figure is fresh on a cache hit; - panels and vitals are each kept by their own identity; - nothing is cached inside a transaction; - the waiting line still misses after a write.

  • test/home-builders-idle.test.ts is back to its original form. A new test in it checks that when a run ends, home and the terminal both say "0 of 1 · ceiling reached".
  • test/observability-budget.test.ts now says in a comment that its budget measures the cold path, a miss on every cache.

Smaller points

  • cachedKpis and cachedVitals no longer take a compute parameter.
  • setHomeClock is the one test hook left, and docs/CACHING.md names it.
  • HOME_MAX_AGE_MS keeps its name because src/web/studio/alarm_lines.ts imports it; its comment now says it is only the 30 s limit for the write-stamped caches (the waiting line and the alarm lines).

Not done: the "Crew spend today" tile is still cached. It shows spend against the forge-wide ceiling, while the idle reason checks each repository's own ceiling, so the two can disagree with or without this cache. That fits the stale-refresh issue that comes next.

Checks: after the merge, bun x tsc --noEmit is clean and the 107 tests in the home, KPI, terminal-dashboard and budget files pass.

To try it: load / as an admin twice within a minute. The second response's Server-Timing shows only kpis.builders, not the other KPI parts.

🤖 Generated with Claude Code

commented

After the last time a person opens home, it keeps reusing the KPIs, panels and vitals it drew for 60 seconds, even through writes. The exception is the builders figure, which is read fresh next to the idle reason (#7624). This update merges main, which added home's remembered regions (#7660).

The one conflict was in test/observability-budget.test.ts, in the cold-home budget test. The branch emptied the KPI, panel and vitals caches by hand; main called forgetHome(app.db) to drop the remembered regions instead. These are separate stores, so the test now clears both and the comment says so. That keeps the cold path a miss on every one of them, so the budget measures a cold home and not a warm hit.

Checks: scripts/gate.ts and bun x tsc --noEmit pass. bun test passes on test/observability-budget.test.ts, test/home-builders-idle.test.ts, test/home-cache.test.ts and test/home-frame.test.ts (20 tests).

🤖 Generated with Claude Code

Approved

Approved. I could not diff against 92b9b53c9120 because the clone lacks that commit, so I read the whole change. The preview answers 200 at /. The earlier asks are met. The builders figure is read fresh on a cache hit, so it agrees with the idle reason. The tests are no longer bent to fit the cache, and a new test checks that home and the terminal agree. The terminal dashboard reads readKpis uncached, so both doors stay consistent. The waiting line stays exact, nothing is cached inside a transaction, and the docs name the one test seam.

Two things I would do next:

  1. Non-blocking: test/observability-budget.test.ts has two near-duplicate comment lines ("what this budget measures..."). Keep one.
  2. Non-blocking: the "Crew spend today" tile can still be up to 60 s stale against the idle reason. Do it in the stale-refresh issue, as the pull already says.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs