Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 9 additions & 20 deletions src/tui/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
themes,
throughputColumns
} from './format.ts'
import { isFiveHourUsageWindow, visibleUsageWindows } from './usage-windows.ts'

type Tab = 'accounts' | 'analytics' | 'settings'
const TABS: readonly Tab[] = ['accounts', 'analytics', 'settings']
Expand Down Expand Up @@ -186,7 +187,7 @@ function accountsWidth(ctx: Ctx, snapshot: DashboardSnapshot): number {
const state = snapshot.providers.find(s => s.provider === account.provider)
const hidden = state?.policy.hiddenWindowIds ?? []
const usage = snapshot.usage.find(u => u.accountId === account.id)
const visible = visibleWindows(usage?.windows ?? [], hidden)
const visible = visibleUsageWindows(usage?.windows ?? [], hidden)
const tag = ctx.tier === 'compact' ? null : planTag(account.plan)
const base =
3 +
Expand All @@ -209,10 +210,6 @@ function accountsWidth(ctx: Ctx, snapshot: DashboardSnapshot): number {
return Math.min(CONTENT_MAX, ctx.columns - 2, widest + 2)
}

function isFiveHour(window: UsageWindow): boolean {
return /5 ?h/i.test(window.label) || window.id === 'session' || window.id === 'five-hour'
}

function recentSwitch(ctx: Ctx, state: ProviderState | undefined): boolean {
if (state?.switchedAt == null) {
return false
Expand All @@ -237,7 +234,10 @@ function orderedRows(snapshot: DashboardSnapshot): Row[] {
const hidden = state?.policy.hiddenWindowIds ?? []
const pressure = (accountId: string): number => {
const windows = snapshot.usage.find(u => u.accountId === accountId)?.windows ?? []
return visibleWindows(windows, hidden).reduce((max, w) => Math.max(max, w.usedPercent), -1)
return visibleUsageWindows(windows, hidden).reduce(
(max, window) => Math.max(max, window.usedPercent),
-1
)
}
const accounts = snapshot.accounts
.filter(account => account.provider === provider)
Expand All @@ -253,17 +253,6 @@ function orderedRows(snapshot: DashboardSnapshot): Row[] {
return rows
}

function visibleWindows(
windows: readonly UsageWindow[],
hiddenIds: readonly string[]
): UsageWindow[] {
const rank = (window: UsageWindow): number =>
isFiveHour(window) ? 0 : /scoped|fable|opus|sonnet|spark/i.test(window.id) ? 2 : 1
return hardWindows(windows)
.filter(window => !hiddenIds.includes(window.id))
.sort((left, right) => rank(left) - rank(right))
}

function windowCell(ctx: Ctx, window: UsageWindow, barWidth: number, withReset: boolean) {
const reset = withReset ? shortReset(window.resetAt, ctx.now) : null
return [
Expand Down Expand Up @@ -337,7 +326,7 @@ function accountLine(
fg: rgb(badge?.color ?? ctx.theme.dim)
})
]
const visible = visibleWindows(usage?.windows ?? [], hiddenIds)
const visible = visibleUsageWindows(usage?.windows ?? [], hiddenIds)
const spend = spendCell(ctx.tier, account, usage)
if (spend !== null) {
children.push(
Expand Down Expand Up @@ -496,7 +485,7 @@ function sessionResets(ctx: Ctx, snapshot: DashboardSnapshot): string | null {
}
const account = snapshot.accounts.find(a => a.id === state.activeAccountId)
const windows = snapshot.usage.find(u => u.accountId === state.activeAccountId)?.windows ?? []
const reset = shortReset(windows.find(isFiveHour)?.resetAt ?? null, ctx.now)
const reset = shortReset(windows.find(isFiveHourUsageWindow)?.resetAt ?? null, ctx.now)
if (account === undefined || reset === null) {
return []
}
Expand Down Expand Up @@ -780,7 +769,7 @@ function providerWindows(snapshot: DashboardSnapshot, provider: ProviderId): Usa
}
}
return [...seen.values()].sort(
(left, right) => (isFiveHour(left) ? 0 : 1) - (isFiveHour(right) ? 0 : 1)
(left, right) => (isFiveHourUsageWindow(left) ? 0 : 1) - (isFiveHourUsageWindow(right) ? 0 : 1)
)
}

Expand Down
40 changes: 40 additions & 0 deletions src/tui/usage-windows.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, test } from 'bun:test'
import type { UsageWindow } from '../domain.ts'
import { visibleUsageWindows } from './usage-windows.ts'

function usageWindow(id: string, label: string): UsageWindow {
return { id, kind: 'hard', label, resetAt: null, usedPercent: 0 }
}

describe('visibleUsageWindows', () => {
test('keeps recognized model limits ahead of unknown limits', () => {
const windows = [
usageWindow('session', '5h session'),
usageWindow('weekly_all', '7 day · all models'),
usageWindow('weekly_scoped:fable', '7 day · Fable'),
usageWindow('nimbus_quill', 'Nimbus Quill')
]

expect(visibleUsageWindows(windows, []).map(window => window.id)).toEqual([
'session',
'weekly_all',
'weekly_scoped:fable',
'nimbus_quill'
])
})

test('shows Fable in the compact pair when the all-model limit is hidden', () => {
const windows = [
usageWindow('session', '5h session'),
usageWindow('weekly_all', '7 day · all models'),
usageWindow('weekly_scoped:fable', '7 day · Fable'),
usageWindow('nimbus_quill', 'Nimbus Quill')
]

expect(
visibleUsageWindows(windows, ['weekly_all'])
.slice(0, 2)
.map(window => window.label)
).toEqual(['5h session', '7 day · Fable'])
})
})
29 changes: 29 additions & 0 deletions src/tui/usage-windows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { UsageWindow } from '../domain.ts'

export function isFiveHourUsageWindow(window: UsageWindow): boolean {
return /5 ?h/i.test(window.label) || window.id === 'session' || window.id === 'five-hour'
}

function usageWindowPriority(window: UsageWindow): number {
const identity = `${window.id} ${window.label}`
switch (true) {
case isFiveHourUsageWindow(window):
return 0
case /^(7 day(?: · all models)?)$/i.test(window.label):
case /^(weekly_all|seven_day|weekly|codex:primary)$/.test(window.id):
return 1
case /scoped|fable|opus|sonnet|spark/i.test(identity):
return 2
default:
return 3
}
}

export function visibleUsageWindows(
windows: readonly UsageWindow[],
hiddenWindowIds: readonly string[]
): UsageWindow[] {
return windows
.filter(window => window.kind === 'hard' && !hiddenWindowIds.includes(window.id))
.sort((left, right) => usageWindowPriority(left) - usageWindowPriority(right))
}
Loading