The catalogue shipped 71 web templates and one EMPTY `mobile` repo, so "build an app" had no starting point on the platform people actually ship to. This is the iOS lane — Expo/React Native and Flutter for cross-platform, SwiftUI for native. The constraint that shaped every file here: an iOS archive needs macOS + Xcode + a signing identity, and our arc fleet has none. hanzoai has 519 registered runners and every one reports `os: linux`; luxfi has 151, same story; zooai has none. The only macOS builds in the org today (hanzoai/world build-desktop.yml) run on GitHub-HOSTED macos-14, which is exactly what the house CI rule says not to do. So `hanzo.yml` declares only what a Linux arc runner can genuinely execute — typecheck + web bundle for Expo, analyze + test + web for Flutter, `swiftc -parse` for SwiftUI — and the macOS archive lives in a separate `workflow_dispatch`-only `ios.yml`. A job pinned to a label no runner advertises would sit queued forever if it fired on push; parking it is honest, a permanently-yellow check is not. The README of every template states the gap and the one command that closes it. Same reasoning on the demos. Expo and Flutter both have a real web target, so `<slug>.hanzo.app` serves the SAME source the simulator runs. SwiftUI has no web target, so those two templates ship with no demo link rather than a screenshot pretending to be one. Upstreams are permissive only and recorded per template: expo/expo templates (MIT), flutter/samples (BSD-3-Clause), XcodeGen (MIT). Second measured gap, found while wiring this up: `hanzo-templates` is not in arcd's `orgs:` list either (hanzoai, luxfi, zooai, parsdao, zenlm), so the Linux jobs would queue with no runner too. Both gaps are one installer re-run, no code change; both are spelled out in every README rather than left to be discovered on the first red build. Third gap, found by probing rather than assuming, and FIXED here: every run in this org died as `startup_failure` with no logs and no annotation. A bare `runs-on: [self-hosted, linux]` probe queued fine while the same job importing hanzoai/ci failed identically at @v1 and at a pinned SHA — so it was never the tag. The org defaulted GITHUB_TOKEN to `read`, and the reusable declares `permissions: packages: write`; a caller cannot exceed the org maximum, so GitHub kills the run before creating a job. hanzo-templates now defaults to `write` like hanzoai/luxfi/zooai, and runs reach `queued` — the honest state, waiting on gap 2.
3.4 KiB
Hanzo Auth
OAuth 2.0 + PKCE sign-in against Hanzo IAM, with a route guard.
Live demo — https://expo-auth.hanzo.app — the same app, rendered by react-native-web from this source. Not a mock-up.
What you get
Authorization-code + PKCE in the system browser via expo-auth-session, tokens
in expo-secure-store (Keychain / Keystore), and the guard in one place in
app/_layout.tsx. Deliberately not a password form: an app that collects the
password owns it, and App Review rejects that for third-party identity.
Run it
npm install
npx expo start # press i for the iOS simulator
npx expo export -p web # the static bundle the live demo serves
Building for iOS — read this first
Our runner fleet cannot produce an iOS build today. Two separate gaps, both measured rather than assumed:
- No macOS runner exists. An archive / IPA needs macOS + Xcode + a signing
identity. Every runner registered to
hanzoai(519 of them) reportsos: linux;luxfihas 151, same;zooaihas none. The only macOS builds in the org today (hanzoai/worldbuild-desktop.yml) run on GitHub-hostedmacos-14, which is what the house CI rule exists to avoid. hanzo-templatesis not in the arcd org list.arcdserveshanzoai, luxfi, zooai, parsdao, zenlm. Until this org is added, even the Linux jobs inhanzo.ymlsitqueuedwith no runner to claim them.
A third gap turned up while wiring this and is already fixed: this org
defaulted GITHUB_TOKEN to read, and hanzoai/ci declares
permissions: packages: write. A caller cannot be granted more than the org
maximum, so every run died as startup_failure before a job was even created —
no logs, no annotation, nothing to read. The org now defaults to write, the
same as hanzoai, and runs reach queued. If you fork these into another org
and see startup_failure with an empty log, check
/orgs/<org>/actions/permissions/workflow first.
So hanzo.yml declares only what a Linux arc runner could run, and the
macOS archive sits in a separate workflow_dispatch-only
.github/workflows/ios.yml — a job pinned to a label nothing advertises would
queue forever if it fired on push.
Both gaps close with the installer, no code change:
# (2) serve this org — re-run on any existing arcd host
~/work/hanzo/arc/scripts/install-arcd.sh \
--labels self-hosted,linux,amd64 \
--orgs hanzoai,luxfi,zooai,parsdao,zenlm,hanzo-templates
# (1) add the macOS lane — on an Apple Silicon Mac, once
xcode-select --install && sudo xcodebuild -license accept
~/work/hanzo/arc/scripts/install-arcd.sh \
--labels self-hosted,macos,arm64,xcode \
--orgs hanzoai,luxfi,zooai,parsdao,zenlm,hanzo-templates
Then runs-on: [self-hosted, macos, arm64, xcode] resolves and ios.yml
archives + exports the IPA. Signing material comes from KMS
(kms.hanzo.ai), never from a checked-in .p12.
CI
hanzo.yml + a seven-line .github/workflows/cicd.yml importing
hanzoai/ci. On every push a Linux arc runner runs:
- typecheck —
npm ci && npx tsc --noEmit - web-bundle —
npx expo export -p web
.github/workflows/ios.yml holds the macOS archive job and is
workflow_dispatch only — see above.
Upstream
Derived from expo/expo · templates/expo-template-tabs (MIT). This template is MIT; the upstream licence travels with the code it came from.