gui.d.ts declares module @hanzogui/web and @hanzogui/core to flow the Hanzo Gui config into every component's style props. Neither was a dependency, so TypeScript read both blocks as ambient declarations of modules that do not exist: the augmentation bound to nothing, the whole shorthand vocabulary stayed untyped, and 29 assignments of bg, p, px, items and justify were rejected as unknown props. Silent, because a declaration for an unresolvable module is legal. Declaring them is what makes the augmentation an augmentation, and it is what the sibling @hanzo/canvas already does. tsc --noEmit goes from 29 errors to none, so prepublishOnly can build. Co-authored-by: Hanzo Dev <[email protected]>
@hanzo/finance-ui
Shared finance data-layer + UI for every Hanzo Finance surface —
finance.hanzo.ai (the tenant shell), the Hanzo Cloud console per-org Finance
module, and the console global-admin finance view. One FinanceClient, one
component set, monochrome, USD cents end to end — so a spend / usage / credits card
looks and behaves identically at every scope. Scope (which org, or all-orgs) is a
property of the injected transport + identity, never a different UI.
Built on the canonical Hanzo 8.x component stack: @hanzo/ui/product (the product
layer — DataTable, Sparkline, LineChart, StatusTag, …) on @hanzo/gui (the
Tamagui primitives). No Tailwind, no shadcn, no Radix, no hand-rolled CSS: every surface
themes off the shared Gui token system, so a finance card reads like any other console
module.
Install
npm i @hanzo/finance-ui @hanzo/ui @hanzo/gui
react / react-dom (≥19) and @hanzo/gui (≥8.0.1) / @hanzo/ui (≥8.0.39)
are peers — the HOST provides the GuiProvider, and it should mount
@hanzo/ui/gui-config (the one canonical scale) rather than a copy of it.
This package ships built dist — ESM with declarations — so a consumer that loads it as
JavaScript rather than through a bundler (a Node BFF route, an edge worker, a vitest
suite) gets a module it can parse. @hanzo/data (a @hanzo/ui peer) still ships TS
source and belongs in your bundler's transpilePackages; as of 8.x @hanzo/ui,
@hanzo/gui and every @hanzogui/* package ship built dist, as does this one.
Use
import { FinanceDashboard, stubFinanceClient, httpFinanceClient } from '@hanzo/finance-ui'
// Preview / landing / local dev — deterministic in-memory data, no backend.
<FinanceDashboard client={stubFinanceClient()} mode="preview" />
// Live — the host injects the transport (its own bearer-scoped fetch to /v1/finance/*).
const client = httpFinanceClient((path, query) => myBearerGet(`finance/${path}`, query))
<FinanceDashboard client={client} />
Swapping stubFinanceClient() ⇄ httpFinanceClient(transport) is a one-line change
with zero UI change — the components never know which one they got.
The /v1/finance/* contract
httpFinanceClient reads, per org (scoped server-side by the caller's bearer):
| Method | Path | Shape |
|---|---|---|
balance() |
GET /v1/finance/balance |
{ availableCents, pendingCents, dueCents, currency, asOf } |
credits() |
GET /v1/finance/credits |
Credit[] |
usage(range) |
GET /v1/finance/usage?range= |
{ totalCents, series[], lines[] } |
invoices() |
GET /v1/finance/invoices |
Invoice[] |
paymentMethods() |
GET /v1/finance/payment-methods |
PaymentMethod[] (masked) |
ledger(range) |
GET /v1/finance/ledger?range= |
LedgerEntry[] (double-entry) |
treasury() |
GET /v1/finance/treasury |
TreasurySummary |
Every payload is run through an optional-safe normalizer: a renamed/absent field
degrades to —, an unconfigured backend yields honest zeros/empty, and a card is
masked to brand + last4 (a PAN can never reach the UI). A rejected read propagates so
the host renders its own honest state — the client never fabricates.
Styling & theming
Monochrome, on the shared @hanzo/gui token system — no CSS import, no hand-rolled
stylesheet. The HOST mounts one GuiProvider (the console + finance.hanzo.ai both do);
the board renders inside it. <FinanceDashboard standalone> (the default) wraps the
board in a Theme scope + page background for a standalone host; pass
standalone={false} inside an app that already mounts its own GuiProvider/Theme
(the console). theme="light" | "dark" pins the scope; otherwise it follows the host.