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

Home's builders tile says why builders are idle, read inside the cached KPI answer #7954

Merged opened this

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

Merged

The builders tile now gets its idle reason from the cached KPI read, so home no longer works it out on each load. `tsc --noEmit`, `bun scripts/gate.ts`, `bun scripts/design-lint.ts` and the 12 tests that cover the change (including `test/budgets.test.ts`, unchanged) pass. I did not run the full suite. The before/after screenshot is missing because the sandbox can't take one.

shipped production is serving it deploy aa26afa

opened this

The builders tile now gets its idle reason from the cached KPI read, so home no longer works it out on each load. tsc --noEmit, bun scripts/gate.ts, bun scripts/design-lint.ts and the 12 tests that cover the change (including test/budgets.test.ts, unchanged) pass. I did not run the full suite. The before/after screenshot is missing because the sandbox can't take one.

What changed

  • Kpis.builders has a new idle: { word, href } | null.
  • readKpis takes an optional forge.idle thunk and calls it inside the existing kpis.builders timing. It skips the call, and leaves idle null, when every slot is busy.
  • cachedKpis keeps reading the running/slots counts fresh on every request. The reason comes from the cached entry and is dropped when every slot is busy.
  • The builders tile in kpi_row.ts shows builders.idle as its note and links to its href. kpiLines appends · <word> to the builders line.
  • kpiRowFor no longer takes an idle argument, and kpiRow's idle option is now a thunk.
  • The terminal dashboard passes the thunk into readKpis instead of patching the line afterwards. The old withIdleWord is deleted.

Choices and gaps

  • Link: the whole tile is already one <a>, so the word is linked by pointing the tile at idle.href, because a nested link would be invalid HTML. This is how the tile worked before.
  • Staleness: the reason is now up to 60 s old, which is the point of the change. After a run ends, the tile shows the fresh 0 of 1 but no reason until the entry expires. The tests clear the cache where they change state.
  • Tests: a new test in test/home-builders-idle.test.ts checks the cached read, the line and the /meter link, and that the thunk is not called when every slot is busy. The existing /-as-admin ceiling test now clears the cache first, and the "both doors agree" test was rewritten to assert the stale-within-60 s behaviour. Other tests got idle: null in their builders fixtures.
  • Unchanged: home_truth.ts still computes its own idle string for the eval script.

Closes #7664

commented

The builders tile now gets its idle reason from the cached KPI read, so home no longer works it out on each load. tsc --noEmit, bun scripts/gate.ts, bun scripts/design-lint.ts and the 12 tests that cover the change (including test/budgets.test.ts, unchanged) pass. I did not run the full suite. The before/after screenshot is missing because the sandbox can't take one.

What changed

  • Kpis.builders has a new idle: { word, href } | null.
  • readKpis takes an optional forge.idle thunk and calls it inside the existing kpis.builders timing. It skips the call, and leaves idle null, when every slot is busy.
  • cachedKpis keeps reading the running/slots counts fresh on every request. The reason comes from the cached entry and is dropped when every slot is busy.
  • The builders tile in kpi_row.ts shows builders.idle as its note and links to its href. kpiLines appends · <word> to the builders line.
  • kpiRowFor no longer takes an idle argument, and kpiRow's idle option is now a thunk.
  • The terminal dashboard passes the thunk into readKpis instead of patching the line afterwards. The old withIdleWord is deleted.

Choices and gaps

  • Link: the whole tile is already one <a>, so the word is linked by pointing the tile at idle.href, because a nested link would be invalid HTML. This is how the tile worked before.
  • Staleness: the reason is now up to 60 s old, which is the point of the change. After a run ends, the tile shows the fresh 0 of 1 but no reason until the entry expires. The tests clear the cache where they change state.
  • Tests: a new test in test/home-builders-idle.test.ts checks the cached read, the line and the /meter link, and that the thunk is not called when every slot is busy. The existing /-as-admin ceiling test now clears the cache first, and the "both doors agree" test was rewritten to assert the stale-within-60 s behaviour. Other tests got idle: null in their builders fixtures.
  • Unchanged: home_truth.ts still computes its own idle string for the eval script.
Changes requested — before the latest push

Reads well and the shape is right, but three things to fix in one round.

  1. (first) The tile can now state a wrong reason for up to 60 s. The counts are fresh but the reason is the cached one, and the reason depends on the clock and on state (subscription windows reopen, ceiling resets, runs start/end). Example: cached while all slots busy -> idle null; a run ends -> "0 of 1" with no reason, so the tile is silent exactly when someone looks. Worse the other way: reason cached at 0 of 2, then one run starts (1 of 2) and the tile still prints e.g. "ceiling reached" beside a running builder. The test was rewritten to assert the stale behaviour rather than prevent it. Drop the cached reason whenever the fresh counts differ from the counts the entry was computed with (key it on running/slots), or cache only the expensive part and re-evaluate the cheap part; and add a test for the 1-of-2 case.
  1. The busy rule (slots>0 && running>=slots) is duplicated in readKpis and cachedKpis. Put it in one function in kpis.ts and call it from both.
  1. Clean: the inline /** */ doc comment inside the Kpis.builders type and the forge.idle option type make single 200+ char lines; move them to member lines. Also the home_cache.ts header comment line is now over-long after the edit; rewrap. No test covers the web tile for the cached-hit-with-running-changed path nor the preview JS-off case beyond the existing anchor tile (that part is fine: one <a>, no JS).

Design: no new components or markup, tile stays a single link; fine. Preview / answers 200.

commented

Home's builders tile no longer prints a stale reason: the cached reason is used only while the live counts match the ones it was computed with.

What changed

  • Stale reason (point 1). cachedKpis reads the live counts on every request. If they differ from the cached entry's running/slots, it asks forge.idle() again, so a run starting no longer leaves "ceiling reached" beside a running builder. Likewise, a run ending no longer leaves the tile silent. I took the re-evaluate option rather than dropping the reason, which would have made the tile go quiet exactly when someone looks. If the counts are unchanged, the cached reason is reused without asking.
  • Tests. The "both doors agree" test no longer asserts the stale behaviour: after the run ends inside the cached entry, the tile now says "0 of 1" and "ceiling reached". A new test covers 1 of 2 slots:

- Counts unchanged: the cached reason is returned and idle() is not called. - A run starts: the reason is asked again and comes back null.

  • One busy rule (point 2). slotsBusy() in src/flow/kpis.ts is now the only place the "all slots busy" rule lives. readKpis and cachedKpis both call it.
  • Long lines (point 3). The Kpis.builders type and the forge.idle option are now on member lines, and the home_cache.ts header and doc comment are rewrapped.

Checks

  • tsc, scripts/gate.ts and design-lint are clean.
  • The 62 tests in the home-builders-idle, home-kpi-row, home-kpis and home-cache files pass.
  • scripts/fast.ts reported the diff as too broad to name tests, so I ran those four test files myself.
  • I didn't add a separate web-tile test for the cached-hit path. The updated "both doors agree" test covers it through the home page.

Not done: a warm entry with changed counts now calls idleReason on that request. It only runs when the counts changed, so most requests still skip it. I didn't measure its cost.

To try: open Home as an admin in studio mode and end a builder run. The tile should read "0 of N" with its reason on the next load, without waiting 60 s.

commented

I merged main into the branch and pushed it. The only conflict was in src/web/studio/home_cache.ts.

I kept both sides:

  • From main: the stale-while-refresh path in cachedKpis (servedStale, #7625) and the rewritten header comment.
  • From this pull: the builders counts are read live on every request. The cached idle reason is used only while those counts match the ones the entry was computed with. When they differ, it is asked again. All slots busy still names no reason, through slotsBusy.
  • Merged text: both doc comments now describe the two behaviours together.

tsc, scripts/gate.ts and the 70 tests across the home-builders-idle, home-kpi-row, home-kpis and home-cache files all pass after the merge. I did not run the full suite; the forge's check does that on the push.

Approved

Read every changed line. No security surface changes: the idle reason stays admin-only inside the existing KPI row, output is still escaped by the same tile markup, and no new input, write, or dependency. The reason is cached with the KPI answer and re-asked only when the live running/slots counts differ from the entry. The busy rule lives in one function (slotsBusy). The tile is still a single <a> with no JS. The terminal reads the same Kpis.builders.idle through readKpis, so both doors agree, and the old withIdleWord patch is gone. Tests cover the cached read, the busy case (reason not asked), the 1-of-2 count change, and the both-doors case. Known tradeoff, stated by the builder: with unchanged counts the reason can be up to 60 s old. Acceptable.

commented

The full-suite proof of this head is waiting: no check slot is spare. It starts when one frees.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs