Files
billing/vitest.config.mts
zooqueen 972b714e24 a panel that could not read says so, and claims nothing
`loadBillingData` fans out ten reads and records a reason per panel. Three
panels rendered theirs — credits, transactions, accounts. The other seven took
the reason and dropped it, so a failed read reached the customer as a fact
about their account: no cards on file, no subscription, no invoices, no spend
alerts, and a balance of $0.

Two of those are statements about money that only a READ balance may make. The
overview printed $0.00 in three places from `balance === null` and fired the
"Your credit balance is $0 — add funds to continue" banner beside it; the
credits panel printed the same zero as a total. Both now render an em dash and
state the reason, because an absent balance is our unknown and never the
customer's debt.

The payment read carried further than its own panel. `setupIncomplete` is built
from the card list and the subscription, so an unread pair made the overview
BECOME the onboarding card — a customer with a card on file walled behind
"add a payment method", which adding a second card would not clear. Setup is
now incomplete only when a successful read says it is, and the overview's
payment tile reads "Unavailable" rather than inviting a purchase it cannot
know is needed.

Each panel takes the shape TransactionsPanel already had: `error` plus
`onRetry`, or `<datum>Error` where one component shows more than one read and a
single slot could not say which failed — the convention `usageError` set. A
component's own submit failure keeps its own slot (`saveError`), so an outage
that happened before the customer clicked is never blamed on their click.

`why` now reads CommerceApiError.status instead of the message alone: 401 and
403 are a refusal and say to sign in again, anything else names the status, and
an error with no status never reached the server and says that plainly.
Measured against api.hanzo.ai: mounted routes answer 401 unauthenticated, so
the three cases are distinguishable at the point the reason is written.

Pinned in lib/billing/panel-errors.test.tsx, both halves, because each was
right alone while the pair was broken: every panel states the reason it is
given and still prints its real emptiness after a successful read, and every
recorded reason reaches a component as a named prop. The prop sites are listed
one by one rather than matched by a pattern — `payment` feeds two components,
and a rule asking only for "at least one site per read" stayed green when
either was deleted.
2026-08-16 22:42:22 -07:00

8 lines
286 B
TypeScript

import { defineConfig } from 'vitest/config'
// `@/` is the app's own root, exactly as tsconfig `paths` declares it. A panel
// imports `@/lib/money`, so a test that renders one needs the same alias.
export default defineConfig({
resolve: { alias: { '@': import.meta.dirname } },
})