Files
hanzo.ai/hooks/useSuite.ts
zooqueenandHanzo Dev 05bcecddb1 /benchmarks' lead holds its four places from first paint, and the gates wait for the page to run before they time it
The lead's tiles and the landing's draw at once when every read has answered
or failed (useSettled); the lead keeps its places at their final height until
then, so its layout shift falls from 0.17-0.42 to under 0.01. Gates that open
/benchmarks, /kai and /templates wait for html[data-hydrated] before their
timed waits, and the console-clean gate answers /v1/event, so a starved runner's
boot no longer spends their budgets.


Co-authored-by: Hanzo Dev <[email protected]>
2026-10-16 06:41:53 -07:00

131 lines
5.3 KiB
TypeScript

'use client'
import { useEffect, useMemo, useRef } from 'react'
import { ready, usePricing, useServed, type Reading } from '@/lib/api/use'
import { table, type Claim, type Kai, type Prices, type Table } from '@/lib/kai'
import { JEV, kaiLead, type Tile } from '@/lib/tally'
import { useEnso } from './useEnso'
import { useKai } from './useKai'
/**
* The Decision Suite as a page draws it: its rows from the research runs
* `useKai` reads, its cost row from the model list (`GET /v1/models`), both read
* when the page opens. The suite waits on the runs alone: until the model list
* answers, or if it does not, the cost row prints a dash and the board states no
* count.
*/
export function useSuite(): Reading<Table> {
const kai = useKai()
const models = ready(useServed())
const rate = (id: string) => models?.find((m) => m.id === id)?.pricing?.input_per_million ?? null
const k = models ? rate('kai') : undefined
const j = models ? rate(JEV) : undefined
return useMemo((): Reading<Table> => {
if (kai.state !== 'ready') return kai
const prices: Prices | null = k === undefined || j === undefined ? null : { kai: k, jev: j }
return { state: 'ready', data: table(kai.data.suite, prices) }
}, [kai, k, j])
}
/**
* The Decision Suite's claim and the runs it was drawn from, once both reads it
* turns on have settled: the runs read, and the model list answered or failed.
* Null until then, so nothing that leads with Kai states the harness count in
* the moment before the suite's. The claim is null when the suite does not hold.
*/
export function useClaim(): { claim: Claim | null; kai: Kai } | null {
const kai = useKai()
const suite = useSuite()
const listed = useServed().state !== 'loading'
return useMemo(
() => (kai.state === 'ready' && suite.state === 'ready' && listed ? { claim: suite.data.claim, kai: kai.data } : null),
[kai, suite, listed],
)
}
/**
* Whether every read the headline figures turn on has answered or failed: the
* research runs (Enso's and Kai's), the model list (through `useClaim`) and the
* price list. The landing's tiles and /benchmarks' lead wait on it and then draw
* at once, so no figure moves another once drawn.
*/
export function useSettled(): boolean {
const runs = useKai().state
const enso = useEnso().state
const priced = usePricing().state !== 'loading'
const said = useClaim()
return priced && enso !== 'loading' && (said !== null || runs === 'unreachable')
}
/**
* Kai's headline figure as every page that leads with it draws it (lib/tally
* `kaiLead`): the Decision Suite's count while it holds, else the harness
* suites Kai leads; nothing until `useClaim` has settled, or when the runs
* cannot be read.
*/
export function useLead(): Tile | null {
const c = useClaim()
return useMemo(() => (c ? kaiLead(c.claim, c.kai) : null), [c])
}
/** How long, in milliseconds from the moment every read has answered, the anchor is held at the top while the sections above it settle. */
const HOLD = 1500
/** What counts as the reader moving the page themselves. */
const MOVES = ['wheel', 'touchstart', 'keydown', 'pointerdown'] as const
/** The address's anchor as an id: the hash decoded where it is well formed, as written where it is not. */
function anchor(): string {
const raw = window.location.hash.slice(1)
try {
return decodeURIComponent(raw)
} catch {
return raw
}
}
/**
* Brings the address's anchor back to the top once the page has stopped
* growing. A link from another page (the landing's Kai tile) arrives before
* anything is read, and the sections above its target grow as the runs, the
* model list and the price list answer. Once all three have, and `drawn` says
* the runs' sections are drawn, the anchor is scrolled to at once, unless the
* reader has moved the page in the meantime: by wheel, touch, key or press
* once the page runs, or before it ran, past the anchor.
*/
export function useAnchor(drawn: boolean): void {
const listed = useServed().state !== 'loading'
const priced = usePricing().state !== 'loading'
const moved = useRef(false)
useEffect(() => {
// The browser's own jump to the anchor is a smooth scroll still under way when the page starts, so only a reader
// already past the anchor is known to have moved before it ran; an anchor with no scroll margin lands a fraction
// of a pixel above the top, which is the jump and not the reader.
const at = document.getElementById(anchor())?.getBoundingClientRect().top
if (at != null && at < -1) moved.current = true
const move = () => {
moved.current = true
}
for (const e of MOVES) window.addEventListener(e, move, { passive: true, once: true })
return () => {
for (const e of MOVES) window.removeEventListener(e, move)
}
}, [])
const settled = drawn && listed && priced
useEffect(() => {
if (!settled || moved.current) return
const go = () => {
if (!moved.current) document.getElementById(anchor())?.scrollIntoView({ block: 'start', behavior: 'instant' })
}
go()
// A chart or a table above it can still re-measure in the frames after; the anchor is held through them.
const watch = new ResizeObserver(go)
watch.observe(document.body)
const done = setTimeout(() => watch.disconnect(), HOLD)
return () => {
clearTimeout(done)
watch.disconnect()
}
}, [settled])
}