Real Hanzo-stack app (Vite + React 19 + @hanzo/gui + @hanzo/iam + @hanzo/base), forked from hanzo-starter with identical provider stack, vite.config, PKCE auth, and deploy contract. Three @hanzo/gui views (proposals list, editor with live totals sidebar, invoice-like preview) over org-scoped proposals + lineitems in Base. tsc --noEmit + vite build green; renders with zero console errors.
4.3 KiB
quotewright — agent notes
Quotewright Proposals: build line-item proposals and share a clean read-only
link. A real Hanzo-stack app forked from hanzo-apps/hanzo-starter — same
provider stack, vite.config.ts, auth, and deploy contract; different product.
Vite + React 19 + @hanzo/gui (UI) + @hanzo/iam (auth) + @hanzo/base (data).
Keep it REAL — every surface must build and run, no fabricated UI.
One way, decomplected
- Providers (
src/providers.tsx) mount in the canonical order every Hanzo surface ships:GuiProvider→IamProvider→BaseProvider.BaseProvidergets aBaseClientcarrying the IAM access token; it is rebuilt when the token changes (src/lib/base.tsbaseAs). That single seam is what makes everyuseQuery/useMutationorg-scoped to the signed-in user. - Env is one place (
src/env.ts), read fromimport.meta.env.VITE_*(VITE_HANZO_CLIENT_IDfalls back tohanzo-app). - Domain is one place (
src/lib/proposal.ts) — theProposal/LineItemtypes, the status vocabulary, the money math (subtotal/taxOf/totalOf/money), andshareLink. Views compute nothing ad-hoc. - UI is one system —
@hanzo/guiprimitives only (no second kit, no Tailwind). Shared pieces live insrc/ui.tsx(Mark,AccentButton,StatusBadge). The three product views are list · editor · preview. - Routing is hand-rolled (
src/views/home.tsx) — no router dep, same ethos as the/auth/callbackgate./p/<id>opens the read-only preview andhistory.pushStatekeeps the URL in sync, so "Copy share link" always matches what's on screen.
Gotchas (do not regress)
@hanzo/guiunder Vite needs three things invite.config.ts(it is the Tamagui line): (1) aliasreact-native→react-native-web, (2)defineprocess.env.TAMAGUI_TARGET/NODE_ENV/__DEV__, (3)dedupereact/react-dom/react-native-web. No Tamagui compiler, noone, no Expo.@hanzo/guiprops are Tamagui LONGHAND with this v5 config:alignItems/justifyContent/backgroundColor/padding/paddingHorizontal/borderRadius/textAlign— NOT theitems/justify/bg/p/rounded/textshorthands. Shorthands pass at runtime but FAILtsc.Buttoncolor goes onButton.Text, not the frame.<Button color=…>does NOT typecheck (colorisn't on the button frame). Put label color/weight on<Button.Text color=… fontWeight=…>, or useAccentButtoninsrc/ui.tsx(the one violet brand button).ButtonusesonPress;Inputusesvalue/onChangeText(+onBlurto persist).- Light "paper" via
<Theme name="light">. The preview document and the hero mock wrap their subtree in<Theme name="light">so theme-relative tokens ($background/$color/$borderColor/$colorN) flip to dark ink on white inside the dark app chrome. Fixed palette tokens ($purple9/$green10/…) read on both. - PKCE storage is
localStorage(not sessionStorage) so the verifier/state survive the round-trip to hanzo.id. schema.sqlis the data contract.proposals+lineitems, org-scoped (@request.auth.org_id = org). Keep it in lockstep withsrc/lib/proposal.tsand the editor/list/preview reads/writes. Tax (8%) is a display convention, not a stored column.
Deploy contract (Hanzo Cloud)
- Static SPA:
npm run build→dist/, served at<slug>.hanzo.appfrom object storage. No server process. - On publish,
schema.sql→provisionBaseFromDDLcreates the collections (org-scoped, IAM-native). Runtime read/write is browser →VITE_HANZO_BASE_URLwith the IAM token. - IAM redirect registration is the one external requirement: the IAM client
(
VITE_HANZO_CLIENT_ID, defaulthanzo-app) must allow this origin's/auth/callback(ahttps://*.hanzo.app/auth/callbackwildcard, or a per-apphanzo-quotewrightclient).
Proven
tsc --noEmit clean · vite build → dist/ · renders under Vite with ZERO
console errors (headless Chromium at 1280×800) · sample invoice totals compute
end-to-end (subtotal $23,300 → +8% tax → $25,164.00) · login() performs a
real PKCE S256 redirect to hanzo.id.
Build
CI (.github/workflows/ci.yml) runs npm ci && npm run typecheck && npm run build — build-verification only, NEVER a container image (Hanzo Cloud owns
deploys; do not build images locally).