@@ -36,7 +36,7 @@ import { buildGraphModel } from "./graph-model.mjs";
3636import { matchesKey } from "./keys.mjs" ;
3737import { box , meter , clip , fmtUsd , makeLineInput } from "./panel.mjs" ;
3838import { makeStyler , frame , RULE } from "./style.mjs" ;
39- import { foldCosts , whatIfFlow } from "./costs.mjs" ;
39+ import { COSTS_WINDOWS , costsSinceMs , foldCosts , whatIfFlow } from "./costs.mjs" ;
4040
4141const KEY_HINTS = "[p]ause [r]esume [q]uit" ;
4242// Fetch the read-model's full window (listRuns clamps at 50) but render a cursor-following viewport of
@@ -56,11 +56,11 @@ const MIN_WIDTH = 8;
5656// Drill-in views (TRIGGER_DETAIL, RUN_DETAIL) are small; they frame to a compact width and center within
5757// the wider overlay rather than stretching a handful of key/value lines across the full LIST width.
5858const DRILL_WIDTH = 70 ;
59- // COSTS: the spend windows `t` cycles through, and the staleness bound the poll tick refreshes against.
60- // The fold itself is cheap, but the scan behind fetchCosts reads EVERY run sidecar in the window -- a
61- // per-second full-directory read is the kind of quiet load a dashboard must not add, so while the view
62- // is open a poll tick refreshes the fold only once the last fetch is older than this.
63- const COSTS_WINDOWS = [ "7d" , "30d" , "mtd" ] ;
59+ // COSTS: the staleness bound the poll tick refreshes against (the `t`-cycled windows themselves are
60+ // costs.mjs' COSTS_WINDOWS -- one list beside the fold whose proration they denominate). The fold is
61+ // cheap, but the scan behind fetchCosts reads EVERY run sidecar in the window -- a per-second
62+ // full-directory read is the kind of quiet load a dashboard must not add, so while the view is open a
63+ // poll tick refreshes the fold only once the last fetch is older than this.
6464const COSTS_STALE_MS = 10_000 ;
6565// GRAPH: the cursor-following row window, on the RUNS_VIEWPORT/TAIL_VIEWPORT precedent -- a fixed
6666// bound with no height dependency, so an unknown terminal height changes nothing. The graph REFRESHES
@@ -133,11 +133,12 @@ export function createDashboardDeps(paths: any) {
133133 */
134134 fetchCosts ( { windowKey } : any ) {
135135 const nowMs = Date . now ( ) ;
136- const records = scanRunRecords ( { logsDir : paths . logsDir , sinceMs : costsWindowSinceMs ( windowKey , nowMs ) , nowMs } ) ;
136+ const sinceMs = costsSinceMs ( windowKey , nowMs ) ;
137+ const records = scanRunRecords ( { logsDir : paths . logsDir , sinceMs, nowMs } ) ;
137138 if ( ! Array . isArray ( records ) ) return { unreachable : ( records as any ) ?. unreachable ?? "scan failed" } ;
138139 const subsView : any = readSubscriptions ( { subscriptionsPath : paths . subscriptionsPath } ) ;
139140 const subscriptions = Array . isArray ( subsView ?. subscriptions ) ? subsView . subscriptions : [ ] ;
140- const fold = foldCosts ( { records, subscriptions, pricing, nowMs, piAiPin : pricing . piAiVersion ( ) } ) ;
141+ const fold = foldCosts ( { records, subscriptions, pricing, nowMs, piAiPin : pricing . piAiVersion ( ) , sinceMs } ) ;
141142 return { fold, records, subscriptions } ;
142143 } ,
143144 /**
@@ -353,7 +354,7 @@ export function makeDashboard({
353354 wi . target = wi . targets [ wi . index ] ?? null ;
354355 wi . result =
355356 wi . target !== null && typeof deps ?. whatIf === "function"
356- ? deps . whatIf ( { records : costs . data ?. records ?? [ ] , flow : wi . flow , target : wi . target } )
357+ ? deps . whatIf ( { records : costs . data ?. records ?? [ ] , flow : wi . flowKey , target : wi . target } )
357358 : null ;
358359 } ;
359360
@@ -716,7 +717,10 @@ export function makeDashboard({
716717 const row = ( costs . data ?. fold ?. byFlow ?? [ ] ) [ costsSel ] ;
717718 const targets = whatIfTargets ( costs . data ) ;
718719 if ( ! row || targets . length === 0 ) return ;
719- costs . whatIf = { flow : row . flow , targets, index : 0 , filter : false , input : null } ;
720+ // flowKey is the MACHINE key (null for the no-flow bucket -- whatIfFlow matches `flow ?? null`,
721+ // and the "(no flow)" display label matches no record); flowLabel is what the header prints.
722+ // The `in` check keeps a fold without flowKey working: for real flows the two are identical.
723+ costs . whatIf = { flowKey : "flowKey" in row ? row . flowKey : row . flow , flowLabel : row . flow , targets, index : 0 , filter : false , input : null } ;
720724 computeWhatIf ( ) ;
721725 tui ?. requestRender ?.( ) ;
722726 return ;
@@ -1789,17 +1793,6 @@ function graphHints(inner: number, styler: any): string {
17891793
17901794// ── COSTS view (issue #53): full-width like LIST, every body line exactly `inner` visible columns ──────
17911795
1792- /** The scan cutoff for a COSTS window key: a fixed span for "7d"/"30d", the start of the current UTC
1793- * month for "mtd" -- the calendar month is the unit subscriptions are priced in, so "how is this month
1794- * going" needs the month's own left edge, not a sliding 30 days. */
1795- function costsWindowSinceMs ( windowKey : string , nowMs : number ) : number {
1796- const day = 24 * 60 * 60 * 1000 ;
1797- if ( windowKey === "7d" ) return nowMs - 7 * day ;
1798- if ( windowKey === "30d" ) return nowMs - 30 * day ;
1799- const now = new Date ( nowMs ) ;
1800- return Date . UTC ( now . getUTCFullYear ( ) , now . getUTCMonth ( ) , 1 ) ;
1801- }
1802-
18031796const MONTHS = [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" ] ;
18041797
18051798/** The frame-title window label: `Aug 2026 (mtd)` for the calendar window, `last 7d`/`last 30d` else. */
@@ -1825,6 +1818,9 @@ function whatIfTargets(data: any): any[] {
18251818 const add = ( provider : any , id : any ) => {
18261819 if ( typeof provider !== "string" || typeof id !== "string" ) return ;
18271820 if ( provider === "unknown" || id === "unknown" ) return ;
1821+ // The ledger's 8-row overflow bucket is an aggregation artifact, not a model: getPricedModel of
1822+ // ("other","other") is null, so offering it would silently degrade the estimate to the seeded band.
1823+ if ( provider === "other" && id === "other" ) return ;
18281824 const key = `${ provider } /${ id } ` ;
18291825 if ( seen . has ( key ) ) return ;
18301826 seen . add ( key ) ;
@@ -2043,7 +2039,7 @@ function costsTableLines(fold: any, table: string, costsSel: number, inner: numb
20432039function whatIfLines ( wi : any , data : any , inner : number , styler : any ) : string [ ] {
20442040 const out : string [ ] = [ ] ;
20452041 const t = wi . target ;
2046- const header = styler . bold ( styler . fg ( "accent" , `WHAT-IF ${ wi . flow } → ${ t ? `${ t . provider } /${ t . id } ` : "?" } ` ) ) +
2042+ const header = styler . bold ( styler . fg ( "accent" , `WHAT-IF ${ wi . flowLabel } → ${ t ? `${ t . provider } /${ t . id } ` : "?" } ` ) ) +
20472043 styler . fg ( "dim" , ` (${ wi . index + 1 } /${ wi . targets . length } · w next · / search)` ) ;
20482044 out . push ( fitLine ( header , inner , styler ) ) ;
20492045 const r = wi . result ;
@@ -2054,11 +2050,11 @@ function whatIfLines(wi: any, data: any, inner: number, styler: any): string[] {
20542050 // like an answer. The note names the tracking id so the band cannot pass as a measurement.
20552051 out . push ( fitLine ( styler . fg ( "warning" , `~~${ fmtUsd ( r . low ) } –${ fmtUsd ( r . high ) } seeded` ) + styler . fg ( "dim" , ` · ${ r . note ?? "unmeasured (OQ-002)" } ` ) , inner , styler ) ) ;
20562052 } else {
2057- const current = ( data ?. fold ?. byFlow ?? [ ] ) . find ( ( x : any ) => x . flow === wi . flow ) ?. cost ?. usd ;
2053+ const current = ( data ?. fold ?. byFlow ?? [ ] ) . find ( ( x : any ) => x . flow === wi . flowLabel ) ?. cost ?. usd ;
20582054 const delta = typeof current === "number" ? r . usd - current : null ;
20592055 const deltaPart = delta === null ? "" : ` (${ delta <= 0 ? "−" : "+" } ${ fmtUsd ( Math . abs ( delta ) ) } vs current)` ;
20602056 out . push ( fitLine ( styler . fmtCost ( { usd : r . usd , class : "estimated" , floor : false } ) + styler . fg ( "dim" , `${ deltaPart } · rates@${ r . ratesVersion ?? "?" } ` ) , inner , styler ) ) ;
2061- const dom = dominantProvider ( data ?. records , wi . flow ) ;
2057+ const dom = dominantProvider ( data ?. records , wi . flowKey ) ;
20622058 if ( t && dom !== null && t . provider !== dom ) {
20632059 out . push ( fitLine ( styler . fg ( "dim" , "cross-provider: same token profile, tokenizers differ — directional only" ) , inner , styler ) ) ;
20642060 }
@@ -2105,6 +2101,9 @@ function provenanceLine(fold: any, inner: number, styler: any): string {
21052101 const prov = fold . provenance ?? { } ;
21062102 let text = `~ estimates at pi-ai ${ prov . piAiPin ?? "?" } · ${ prov . runsUnmetered ?? 0 } runs unmetered · ${ prov . runsUnledgered ?? 0 } not repriceable` ;
21072103 if ( ( prov . ratesDrifted ?? 0 ) > 0 ) text += ` · ${ prov . ratesDrifted } priced under older rates` ;
2104+ // Only when it happened, like the drift counter: a fanout past the 8-row ledger cap loses per-model
2105+ // attribution, and the by-model table must not look complete while rows hide inside `other`.
2106+ if ( ( prov . runsLedgerTruncated ?? 0 ) > 0 ) text += ` · ${ prov . runsLedgerTruncated } ledgers truncated` ;
21082107 return fitLine ( styler . fg ( "dim" , text ) , inner , styler ) ;
21092108}
21102109
0 commit comments