What this is
Tracking issue for shipping bench-review as a native iOS app, following the pattern the wildloop sibling app already uses (stacks @stacksjs/mobile + craft-native).
It is not a rewrite. ./buddy build:ios generates an Xcode project whose app is a native shell loading this same STX site — one set of views, one set of routes, one deploy. Which server it loads is fixed at build time, with a bundled copy of the site as an offline fallback: if the server is unreachable (deploy, dropped connection, no signal) the app serves its own pages and returns to the server once it answers.
Already landed
chore(deps): upgrade stacks to 0.70.380 and add @stacksjs/mobile—@stacksjs/mobilefirst exists at 0.70.375, so thestackspin had to move off 0.70.190.craft-nativeis pinned throughoverridesbecause@stacksjs/actionsotherwise nestscraft-native@0.0.55, whose exports map has no./iossubpath, and the builder's ownimport('craft-native/ios')resolves to that nested copy and fails.feat(mobile): add the iOS app config, icon, and ignore the generated project—config/mobile.ts, a 1024² alpha-free icon rendered frompublic/images/logos/logo.svg, and an ignore rule for the generated project.feat(mobile): ship a lean offline bundle instead of all 3,593 pre-rendered pages— see below.
Current state: ** BUILD SUCCEEDED ** against the iPhone 17 Pro simulator SDK, producing a 13 MB .app.
The offline-bundle finding, since it shapes the rest
Pointing the offline copy at dist/ produced a 443 MB app — dist/ pre-renders a page per entity (290 MB of judges, 143 MB of courthouses, 121 KB × 3,593 pages). That fails App Store cellular download limits outright.
Those pages buy nothing offline: the API is unreachable then too, and bench renders entity content client-side from API data, so a bundled judges/1803.html shows the same empty state as the shared judges/_shell/* that is kept. scripts/build-offline-bundle.ts strips the per-entity trees:
| before | after | |
|---|---|---|
| bundle | 430 MB | 10.5 MB (−97.6%) |
| pages | 3,593 | 45 |
.app | 443 MB | 13 MB |
It fails above an 80 MB ceiling, so a future per-entity route missing from ENTITY_TREES surfaces as a build error rather than a quietly enormous app.
What is left
Filed separately, linked below:
- #80 — universal links are entitled but no
apple-app-site-associationis served - #81 — no end-to-end tests for the native app (navigation, deep links, offline cold start)
- #82 — no physical-device build path; everything so far is Simulator-only
- #83 — courthouse tab routes have no shared shell, so they 404 offline
- #84 — App Store submission readiness (privacy manifest gaps, Connect answers, UGC review notes)
- #85 —
benchreview.orgis not deployed (blocks #80 and #82)
Adjacent, not mobile-specific but surfaced by this work: #86 (25 of 33 declared foreign keys missing from the live schema).
Android is deliberately out of scope for now; config/mobile.ts has an android section available when wanted.
Reference
~/Documents/Stacks/wildloop — same framework, mobile already shipped. Its scripts/run-mobile-e2e.ts (509 lines), scripts/run-ios-device.ts (276), scripts/generate-mobile-associations.ts (58) and .maestro/flows/ are the templates for the items above.