mirror of
https://github.com/hanzo-apps/dispatch.git
synced 2026-10-30 19:23:31 +00:00
A real Hanzo-stack starter template: public newsprint masthead + subscribe, signed-in archive/issue reader, and a Studio composer over org-scoped Base collections (issues + subscribers). Vite + React 19 + @hanzo/gui + @hanzo/iam + @hanzo/base; PKCE against hanzo.id; provisioned from schema.sql. tsc + vite build green.
3.8 KiB
3.8 KiB
dispatch-newsletter — agent notes
Dispatch — a newsletter archive + subscribe on Hanzo, forked from the canonical
hanzo-starter. Vite + React 19 + @hanzo/gui (UI) + @hanzo/iam (auth) +
@hanzo/base (data). Keep it minimal and REAL — every surface must build and
run, no fabricated UI.
The app
- Three views: Landing (public masthead + subscribe), Archive → Issue (signed-in reading), Studio (signed-in compose + subscriber list).
- Two Base collections from
schema.sql:issues(subject/preview/body/sent_at/published) andsubscribers(email/name/subscribed_at/status). - Design: newsprint minimalism — off-white paper, one hairline ink rule under
the wordmark, a single narrow column (
measure= 640), a boxed subscribe field with a solid black button. The palette lives in ONE place,src/theme.ts, and is passed as explicit color values to@hanzo/guiprimitives.
One way, decomplected
- Providers (
src/providers.tsx) mount in the canonical order:GuiProvider→IamProvider→BaseProvider.BaseProvidergets aBaseClientcarrying the IAM access token, rebuilt when the token changes (src/lib/base.tsbaseAs). That single seam makes everyuseQuery/useMutationorg-scoped to the signed-in user. - Env is one place (
src/env.ts). Client id readsimport.meta.env.VITE_IAM_CLIENT_ID(fallbackhanzo-app). - Palette is one place (
src/theme.ts). UI is one system —@hanzo/guiprimitives only (no second kit, no Tailwind). - Subscribe is one flow: the landing stashes the email in
localStorage(PENDING_KEY) and starts PKCE;shell.tsxfinalizes it into asubscribersrow on return (auseRefguard + immediateremoveItemclaim it once, so StrictMode's double-invoke can't create a duplicate).
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:alignItems/justifyContent/backgroundColor/padding/alignSelf/borderRadius/textAlign— NOT theitems/justify/bg/p/self/rounded/textshorthands. Shorthands pass at runtime but FAILtsc.ButtonusesonPress;Input/TextAreausevalue/onChangeText.Buttonis a Stack frame — it does NOT accept text props (color/fontSize/fontWeight). Style the label with a<SizableText>child; set the fill viabackgroundColoron the Button.TextArearejectslineHeight(usefontSize/padding/minHeight).- PKCE storage is
localStorageso the verifier/state survive the round-trip to hanzo.id. schema.sqlis the data contract (databaseSchema→provisionBaseFromDDL→ Base collections). Keep it in lockstep witharchive.tsx/issue.tsx/studio.tsx.
Deploy contract (Hanzo Cloud)
- Static SPA:
npm run build→dist/, served at<slug>.hanzo.app. No server. - On publish,
schema.sql→provisionBaseFromDDLcreates the collections (org-scoped, IAM-native, rule effectively@request.auth.org_id = org). - IAM redirect registration is the one external requirement: the IAM client
(
VITE_IAM_CLIENT_ID, defaulthanzo-app) must allow this origin's/auth/callback(per-apphanzo-dispatchclient provisioned at deploy, or ahttps://*.hanzo.app/auth/callbackwildcard on the shared client).
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).