Vite + React 19 + @hanzo/gui + @hanzo/iam + @hanzo/base, forked from the canonical hanzo-starter. One org-scoped Base collection (items) behind four @hanzo/gui surfaces: a dense sortable inventory table (amber Low rows, red Out), item detail with stock adjust + reorder point, low-stock alerts, and a manage writer. tsc + vite build green.
5.0 KiB
Stockroom (inventory-stockroom) — agent notes
A dense inventory tracker with low-stock alerts, forked from the canonical Hanzo
starter. Vite + React 19 + @hanzo/gui (UI) + @hanzo/iam (auth) + @hanzo/base
(data). Keep it REAL — every surface must build and run, no fabricated UI.
The app
One org-scoped Base collection, items(sku, name, qty, reorder_at, location),
behind four surfaces:
- Inventory (
src/views/inventory.tsx) — a dense, sortable, searchable data-table; the only reader. Rows flag themselves viastatusOf: amber when on-hand ≤reorder_at(Low), red at zero (Out). Click a row → detail. - Item detail (
src/views/detail.tsx) — status, on-hand vs reorder point, suggested order size; the per-item writer: adjust qty (receive/pick), change the reorder point, delete (useMutation('items','update'|'delete')). - Alerts (
src/views/alerts.tsx) —lowStock(items): Low/Out only, Out first then deepest deficit, each withreorderQty. Click → detail. - Manage (
src/views/manage.tsx) — the create writer: add a SKU, then jump back to Inventory. - Landing (
src/views/signed-out.tsx) — the honest public view; an illustrative preview table (same row styling as the real grid, not live data) plus the PKCE sign-in. This is the catalog thumbnail. src/lib/stock.ts— theItemtype, the warehouse palette (AMBER/RED/GREEN/PANEL/LINE),num,statusOf/statusRank/deficit/severity,reorderQty,lowStock,totalUnits,tint. One place for stock logic.
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; rebuilt when the token changes (src/lib/base.tsbaseAs). That single seam scopes everyuseQuery/useMutationto the signed-in user's org. - One query —
HomeownsuseQuery<Item>('items')and passesitems+refetchdown; writersrefetch()after each mutation (realtime off — one simple path).Detailis keyed byitem.idso it remounts per item. - Env is one place (
src/env.ts), read fromimport.meta.env.VITE_*. The IAM client id isVITE_HANZO_CLIENT_ID(fallbackhanzo-app). - UI is one system —
@hanzo/guiprimitives only (no second kit, no Tailwind).
Gotchas (do not regress)
@hanzo/guiunder Vite needs three things invite.config.ts(it is the Tamagui line; the in-browser builder runtime can't do this, which is the whole reason this ships as a real repo): (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 — the optimizer is a perf pass, not a correctness one.@hanzo/guiprops are Tamagui LONGHAND with this v5 config:alignItems/justifyContent/backgroundColor/padding/paddingHorizontal/paddingVertical/alignSelf/borderRadius/borderLeftWidth/textAlign— NOT theitems/justify/bg/p/px/py/self/rounded/textshorthands. Shorthands pass at runtime but FAILtsc.Buttonand pressable stacks useonPress;Inputusesvalue/onChangeText/onSubmitEditing. Table rows / alert cards are pressableXStacks (onPress+hoverStyle+pressStyle+cursor). Status hues are raw hex (#f59e0b, hex8 tints like#f59e0b26) — Tamagui accepts them.- PKCE storage is
localStorage(not sessionStorage) so the verifier/state survive the round-trip to hanzo.id. schema.sqlis the data contract. It is thedatabaseSchemaDDL the deploy translates into theitemsBase collection (provisionBaseFromDDL). Numeric fields (qty,reorder_at) can arrive as strings — always read them throughnum(). Keep the DDL in lockstep withsrc/lib/stock.ts+ the views.
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 theitemscollection (org-scoped via@request.auth.org_id = org, 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. The deploy provisions a per-apphanzo-<app>client.
Proven
npm run typecheck (tsc --noEmit) clean · npm run build (tsc + vite) →
dist/ · the landing renders under headless Chrome at 1280×800 with zero
console errors, showing the branded hero + the amber/red preview stock table.
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).