Skip to content

Commit e61dc59

Browse files
fix: enforce non-null assertion lint rule
1 parent 79e4ed4 commit e61dc59

8 files changed

Lines changed: 171 additions & 137 deletions

File tree

‎opencode.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
"bun run format *": "allow",
1111
"bun run format:fix *": "allow",
1212
"bun run test *": "allow",
13-
"bun run build *": "allow"
13+
"bun run build *": "allow",
14+
"bunx biome check *": "allow",
15+
"bunx biome format *": "allow"
1416
}
1517
}
1618
}

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
},
2323
"files": ["dist", "!dist/**/*.map", "README.md", "LICENSE"],
2424
"scripts": {
25-
"biome": "biome check --skip=noNonNullAssertion",
25+
"biome": "biome check",
2626
"biome:fix": "biome check --fix",
2727
"build": "bun run typecheck && bun run build.ts",
2828
"ci": "bun run clean && bun install --frozen-lockfile && bun test && bun run build && bun run package:check && bun run renovate:config && bun pm pack --ignore-scripts && npm i --omit=dev --dry-run *.tgz",

‎src/dialog.tsx‎

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
spendRatio,
2727
spendSeverity
2828
} from './format'
29-
import type { RequestyStore } from './state'
29+
import type { RequestyData, RequestyStore } from './state'
3030
import type { RequestyTheme } from './ui-types'
3131

3232
/** Model rows shown in the scrollbox before it scrolls. */
@@ -75,9 +75,13 @@ export function RequestyDetailDialog(props: DetailDialogProps): JSX.Element {
7575
when={data()}
7676
fallback={<CenteredMessage theme={theme()}>{state().status === 'loading' ? 'Loading Requesty usage…' : 'No data yet.'}</CenteredMessage>}
7777
>
78-
<KpiRow store={props.store} theme={theme()} thresholds={props.thresholds} />
79-
<BudgetSection store={props.store} theme={theme()} thresholds={props.thresholds} />
80-
<ModelSection store={props.store} theme={theme()} />
78+
{(snapshot) => (
79+
<>
80+
<KpiRow data={snapshot()} theme={theme()} thresholds={props.thresholds} />
81+
<BudgetSection data={snapshot()} theme={theme()} thresholds={props.thresholds} />
82+
<ModelSection data={snapshot()} theme={theme()} />
83+
</>
84+
)}
8185
</Show>
8286
</box>
8387
</box>
@@ -91,9 +95,11 @@ function Title(props: { store: RequestyStore; theme: RequestyTheme; showKeyName:
9195
return (
9296
<text fg={props.theme.text}>
9397
<Show when={props.store.data()} fallback={<strong>Requesty</strong>}>
94-
<a href={analyticsUrl(props.store.data()!.keyInfo.name)}>
95-
<strong>Requesty{props.showKeyName ? ` (${props.store.data()!.keyInfo.name})` : ''}</strong>
96-
</a>
98+
{(data) => (
99+
<a href={analyticsUrl(data().keyInfo.name)}>
100+
<strong>Requesty{props.showKeyName ? ` (${data().keyInfo.name})` : ''}</strong>
101+
</a>
102+
)}
97103
</Show>
98104
</text>
99105
)
@@ -107,8 +113,8 @@ function CenteredMessage(props: { children: string; theme: RequestyTheme; error?
107113
)
108114
}
109115

110-
function KpiRow(props: { store: RequestyStore; theme: RequestyTheme; thresholds: SpendThresholds }): JSX.Element {
111-
const data = () => props.store.data()!
116+
function KpiRow(props: { data: RequestyData; theme: RequestyTheme; thresholds: SpendThresholds }): JSX.Element {
117+
const data = () => props.data
112118
const limit = () => data().keyInfo.monthly_limit
113119
const spend = () => data().keyInfo.monthly_spend
114120
const ratio = () => spendRatio(spend(), limit())
@@ -125,27 +131,29 @@ function KpiRow(props: { store: RequestyStore; theme: RequestyTheme; thresholds:
125131
<Metric label="Remaining" value={formatUsd(limit() - spend())} theme={props.theme} color={severityColor(severity(), props.theme)} />
126132
</Show>
127133
<Show when={projectionParts()}>
128-
<TrendMetric
129-
label={`End of Month (${projectionMethodLabel(projectionParts()!.method)})`}
130-
value={`~${formatUsd(projectionParts()!.projected)}`}
131-
theme={props.theme}
132-
color={isProjectionOverLimit(spend(), limit(), new Date(), projection()) ? props.theme.error : props.theme.text}
133-
indicator={
134-
projectionParts()!.arrow ? { text: projectionParts()!.arrow, color: paceColor(projectionParts()!.pace, props.theme) } : undefined
135-
}
136-
/>
134+
{(parts) => (
135+
<TrendMetric
136+
label={`End of Month (${projectionMethodLabel(parts().method)})`}
137+
value={`~${formatUsd(parts().projected)}`}
138+
theme={props.theme}
139+
color={isProjectionOverLimit(spend(), limit(), new Date(), projection()) ? props.theme.error : props.theme.text}
140+
indicator={parts().arrow ? { text: parts().arrow, color: paceColor(parts().pace, props.theme) } : undefined}
141+
/>
142+
)}
137143
</Show>
138144
<Show when={monthDelta()}>
139-
<TrendMetric
140-
label="last month"
141-
value={formatUsd(data().lastMonthSpend)}
142-
theme={props.theme}
143-
color={props.theme.text}
144-
indicator={{
145-
text: `${monthDelta()!.arrow} ${monthDelta()!.sign}${monthDelta()!.pct}%`,
146-
color: monthDelta()!.pct > 0 ? props.theme.error : monthDelta()!.pct < 0 ? props.theme.success : props.theme.textMuted
147-
}}
148-
/>
145+
{(delta) => (
146+
<TrendMetric
147+
label="last month"
148+
value={formatUsd(data().lastMonthSpend)}
149+
theme={props.theme}
150+
color={props.theme.text}
151+
indicator={{
152+
text: `${delta().arrow} ${delta().sign}${delta().pct}%`,
153+
color: delta().pct > 0 ? props.theme.error : delta().pct < 0 ? props.theme.success : props.theme.textMuted
154+
}}
155+
/>
156+
)}
149157
</Show>
150158
</box>
151159
)
@@ -156,7 +164,7 @@ function Metric(props: { label: string; value: string; theme: RequestyTheme; col
156164
<box flexDirection="column" flexGrow={1} flexBasis={0}>
157165
<text fg={props.color}>
158166
<strong>{props.value}</strong>
159-
<Show when={props.tokens}> {formatTokenInline(props.tokens!.input, props.tokens!.output)}</Show>
167+
<Show when={props.tokens}>{(tokens) => <> {formatTokenInline(tokens().input, tokens().output)}</>}</Show>
160168
</text>
161169
<text fg={props.theme.textMuted}>{props.label}</text>
162170
</box>
@@ -178,16 +186,20 @@ function TrendMetric(props: {
178186
<text fg={props.theme.textMuted}>
179187
{props.label}
180188
<Show when={props.indicator}>
181-
{' '}
182-
<span style={{ fg: props.indicator!.color }}>{props.indicator!.text}</span>
189+
{(indicator) => (
190+
<>
191+
{' '}
192+
<span style={{ fg: indicator().color }}>{indicator().text}</span>
193+
</>
194+
)}
183195
</Show>
184196
</text>
185197
</box>
186198
)
187199
}
188200

189-
function BudgetSection(props: { store: RequestyStore; theme: RequestyTheme; thresholds: SpendThresholds }): JSX.Element {
190-
const data = () => props.store.data()!
201+
function BudgetSection(props: { data: RequestyData; theme: RequestyTheme; thresholds: SpendThresholds }): JSX.Element {
202+
const data = () => props.data
191203
const limit = () => data().keyInfo.monthly_limit
192204
const spend = () => data().keyInfo.monthly_spend
193205
const ratio = () => spendRatio(spend(), limit())
@@ -261,8 +273,8 @@ function StatusBadge(props: { overBudget: boolean; theme: RequestyTheme }): JSX.
261273
)
262274
}
263275

264-
function ModelSection(props: { store: RequestyStore; theme: RequestyTheme }): JSX.Element {
265-
const data = () => props.store.data()!
276+
function ModelSection(props: { data: RequestyData; theme: RequestyTheme }): JSX.Element {
277+
const data = () => props.data
266278
const models = () => data().models
267279
const totalSpend = () => models().reduce((sum, model) => sum + model.spend, 0)
268280
const monthSpend = () => data().keyInfo.monthly_spend

‎src/prompt.tsx‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ export function RequestyPromptWidget(props: PromptProps): JSX.Element {
108108
<For each={segments().parts}>
109109
{(seg) => (
110110
<Show when={seg.href} fallback={<span style={{ fg: seg.color }}>{seg.text}</span>}>
111-
<a href={seg.href!} style={{ fg: seg.color }}>
112-
{seg.text}
113-
</a>
111+
{(href) => (
112+
<a href={href()} style={{ fg: seg.color }}>
113+
{seg.text}
114+
</a>
115+
)}
114116
</Show>
115117
)}
116118
</For>

‎src/widget.tsx‎

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
spendRatio,
2121
spendSeverity
2222
} from './format'
23-
import type { RequestyStore } from './state'
23+
import type { RequestyData, RequestyStore } from './state'
2424
import type { RequestyTheme, RequestyWidgetHost } from './ui-types'
2525

2626
/** Width of the budget progress bar in the sidebar. */
@@ -73,9 +73,11 @@ export function RequestySidebarWidget(props: WidgetProps): JSX.Element {
7373
<box flexDirection="column" paddingTop={1}>
7474
<text fg={theme().text}>
7575
<Show when={snapshot()} fallback={<strong>Requesty</strong>}>
76-
<a href={analyticsUrl(snapshot()!.keyInfo.name)}>
77-
<strong>Requesty{props.showKeyName ? ` (${snapshot()!.keyInfo.name})` : ''}</strong>
78-
</a>
76+
{(data) => (
77+
<a href={analyticsUrl(data().keyInfo.name)}>
78+
<strong>Requesty{props.showKeyName ? ` (${data().keyInfo.name})` : ''}</strong>
79+
</a>
80+
)}
7981
</Show>
8082
</text>
8183
<box flexDirection="column" paddingTop={1}>
@@ -84,9 +86,7 @@ export function RequestySidebarWidget(props: WidgetProps): JSX.Element {
8486
fallback={
8587
<box flexDirection="column">
8688
<text fg={theme().error}>Requesty: {props.store.errorMessage() ?? ''}</text>
87-
<Show when={snapshot()}>
88-
<Snapshot {...snapshotProps(true)} />
89-
</Show>
89+
<Show when={snapshot()}>{(data) => <Snapshot {...snapshotProps(true)} data={data()} />}</Show>
9090
</box>
9191
}
9292
>
@@ -98,7 +98,7 @@ export function RequestySidebarWidget(props: WidgetProps): JSX.Element {
9898
</text>
9999
}
100100
>
101-
<Snapshot {...snapshotProps()} />
101+
{(data) => <Snapshot {...snapshotProps()} data={data()} />}
102102
</Show>
103103
</Show>
104104
</box>
@@ -108,6 +108,7 @@ export function RequestySidebarWidget(props: WidgetProps): JSX.Element {
108108

109109
interface SnapshotProps {
110110
store: RequestyStore
111+
data: RequestyData
111112
theme: RequestyTheme
112113
maxModels: number
113114
thresholds: SpendThresholds
@@ -117,7 +118,7 @@ interface SnapshotProps {
117118
}
118119

119120
function Snapshot(props: SnapshotProps): JSX.Element {
120-
const data = () => props.store.data()!
121+
const data = () => props.data
121122
const limit = () => data().keyInfo.monthly_limit
122123
const spend = () => data().keyInfo.monthly_spend
123124
const ratio = () => spendRatio(spend(), limit())
@@ -154,12 +155,16 @@ function Snapshot(props: SnapshotProps): JSX.Element {
154155
<Show when={projectionParts() || props.stale}>
155156
<box flexDirection="row">
156157
<Show when={projectionParts()}>
157-
<text fg={projectionOverLimit() ? props.theme.error : props.theme.textMuted}>
158-
~{formatUsd(projectionParts()!.projected)} EOM{' '}
159-
<Show when={projectionParts()!.arrow}>
160-
<span style={{ fg: paceColor(projectionParts()!.pace, props.theme) }}>{projectionParts()!.arrow}</span>
161-
</Show>
162-
</text>
158+
<Show when={projectionParts()}>
159+
{(parts) => (
160+
<text fg={projectionOverLimit() ? props.theme.error : props.theme.textMuted}>
161+
~{formatUsd(parts().projected)} EOM{' '}
162+
<Show when={parts().arrow}>
163+
<span style={{ fg: paceColor(parts().pace, props.theme) }}>{parts().arrow}</span>
164+
</Show>
165+
</text>
166+
)}
167+
</Show>
163168
</Show>
164169
<Show when={props.stale}>
165170
<text fg={props.theme.textMuted}> (stale)</text>

‎test/logic.test.ts‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,9 @@ describe('weekday projection model', () => {
764764
const model = weekdayProjection(series)
765765
assert.equal(model.method, 'weekday')
766766
// Monday averages $10/day, overall mean is $10/7 → Monday weight = 7.
767-
assert.equal(round(model.weights[1]!), 7)
767+
const mondayWeight = model.weights[1]
768+
assert.ok(mondayWeight !== undefined)
769+
assert.equal(round(mondayWeight), 7)
768770
assert.equal(model.weights[0], 0)
769771
})
770772

@@ -807,7 +809,9 @@ describe('weekday projection model', () => {
807809
})
808810

809811
test('formatProjectionParts reports the model method', () => {
810-
assert.equal(formatProjectionParts(100, 0, aug15, WORKDAY_PROJECTION)!.method, 'workdays')
812+
const projection = formatProjectionParts(100, 0, aug15, WORKDAY_PROJECTION)
813+
assert.ok(projection)
814+
assert.equal(projection.method, 'workdays')
811815
})
812816

813817
test('paceStatus uses the model weights for the elapsed share', () => {
@@ -826,7 +830,9 @@ describe('weekday projection model', () => {
826830
// Workdays method projects $210 vs last month's $210 → no delta, while the
827831
// calendar method would project ≈$206.67 and report a drop.
828832
assert.deepEqual(formatMonthDeltaParts(100, 210, aug15, WORKDAY_PROJECTION), { arrow: '→', sign: '', pct: 0 })
829-
assert.equal(formatMonthDeltaParts(100, 210, aug15, CALENDAR_PROJECTION)!.pct < 0, true)
833+
const delta = formatMonthDeltaParts(100, 210, aug15, CALENDAR_PROJECTION)
834+
assert.ok(delta)
835+
assert.equal(delta.pct < 0, true)
830836
})
831837

832838
test('daysToExhaustion on the calendar method matches the plain division', () => {
@@ -1049,7 +1055,8 @@ describe('sessionRepaintKey', () => {
10491055
test('changes when tokens mutate in place with a constant array length', () => {
10501056
const before = assistant()
10511057
const after = assistant({ tokens: { input: 100, output: 250, reasoning: 10, cache: { read: 5, write: 2 } } })
1052-
assert.equal(before.tokens!.output, 50)
1058+
assert.ok(before.tokens)
1059+
assert.equal(before.tokens.output, 50)
10531060
assert.notEqual(sessionRepaintKey([before]), sessionRepaintKey([after]))
10541061
})
10551062

‎test/prompt.test.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async function renderPrompt(store: RequestyStore, overrides: Partial<PromptProps
2626
const setup = await testRender(() => <RequestyPromptWidget {...BASE_PROPS} store={store} {...overrides} />, { width: 100, height: 2 })
2727
try {
2828
await setup.flush()
29-
return setup.captureCharFrame().split('\n')[0]!.replace(/\s+/g, ' ').trim()
29+
return (setup.captureCharFrame().split('\n')[0] ?? '').replace(/\s+/g, ' ').trim()
3030
} finally {
3131
setup.renderer.destroy()
3232
}

0 commit comments

Comments
 (0)