Repository navigation
feat(ios)!: replace web SPA with React Native app - #2
Conversation
Ship Still as a bare React Native iOS app (io.ponti.still) with the StillCore Swift package: email-OTP sign-in through the public HominemAuth client with an app-private Keychain session, seven screens, and hand-rolled TestFlight archive/deploy scripts plus a runbook. The Vite web app, Docker/nginx image, and Railway config are removed. BREAKING CHANGE: the Still web app is removed. Still ships as an iOS app only.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 464b526123
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| STILL_METRO_PORT = 8084; | ||
| SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; | ||
| SWIFT_VERSION = 5.0; | ||
| TARGETED_DEVICE_FAMILY = "1,2"; |
There was a problem hiding this comment.
Restrict the release target to iPhone
The Release configuration advertises support for iPad, but AppIcon.appiconset/Contents.json contains only iPhone and marketing icons, and the runbook explicitly describes the app as iPhone-only. App Store Connect therefore treats uploads as universal builds while required iPad icon sizes are absent, which can block TestFlight validation; use device family 1 or provide complete iPad assets and support.
Useful? React with 👍 / 👎.
| <FlatList | ||
| data={rows} | ||
| keyExtractor={(i) => i.id} | ||
| scrollEnabled={false} |
There was a problem hiding this comment.
Use the item list as the vertical scroller
This FlatList is rendered inside the vertical ScrollView in App.tsx, and setting scrollEnabled={false} does not preserve FlatList virtualization in that arrangement. For inventories approaching the 5,000-item fetch limit, the screen can create thousands of row views and stall or exhaust memory; make a FlatList/SectionList the top-level scroller rather than nesting it in a same-axis ScrollView.
Useful? React with 👍 / 👎.
| guard address.contains("@"), address.contains("."), !state.busy else { | ||
| return update { $0.problem = .badEmail } |
There was a problem hiding this comment.
Ignore duplicate code requests while busy
When a second request reaches the main actor while the first OTP request is awaiting the network—for example, from a rapid button tap plus keyboard submission—the !state.busy failure is handled as .badEmail. The first request can then succeed without clearing that problem, leaving the code-entry screen showing an incorrect email error; return unchanged when busy and reserve .badEmail for address validation failures.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🟡 Changes recommended
Numeric JSON corruption, incomplete offline sign-out, and iOS release configuration defects must be resolved before approval.
6 open findings
Numeric JSON values are incorrectly encoded as booleans · New Release advertises iPad support without required app icons · New Shared scheme references missing StillTests target · New Queued OTP requests overwrite valid state with bad email · New Sign-out leaves Keychain session after network failure · New Nested FlatList causes large response rendering stalls · New
What changed in this PR
Replaces the Still web SPA with a React Native iOS client backed by StillCore and the Hominem API.
Changes:
- Adds native email-OTP authentication, API bridging, and React Native screens.
- Adds Jest/Swift coverage and iOS project configuration.
- Adds device/TestFlight tooling while removing web deployment infrastructure.
| File | Description |
|---|---|
vite.config.ts |
Removes Vite configuration. |
tsconfig.json |
Adopts React Native TypeScript settings. |
StillCore/Tests/StillCoreTests/StillAccountTests.swift |
Tests account and serialization behavior. |
StillCore/Sources/StillCore/StillAccountService.swift |
Implements Hominem authentication and requests. |
StillCore/Sources/StillCore/StillAccount.swift |
Manages native account state. |
StillCore/Package.swift |
Defines the StillCore package. |
StillCore/Package.resolved |
Pins HominemAuth. |
src/vite-env.d.ts |
Removes Vite environment types. |
src/ui/theme.ts |
Defines native UI colors. |
src/ui/SignIn.tsx |
Adds OTP sign-in screens. |
src/ui/Sheet.tsx |
Adds native bottom sheets. |
src/ui/Overview.tsx |
Adds overview cards and panels. |
src/ui/ItemSheet.tsx |
Adds item editing UI. |
src/ui/ItemList.tsx |
Adds native item lists. |
src/ui/Groups.tsx |
Adds group grids and editing. |
src/style.css |
Removes web styling. |
src/route.ts |
Removes browser routing. |
src/platform/NativeStill.ts |
Defines the native bridge API. |
src/main.tsx |
Removes the browser entry point. |
src/core/useAccount.ts |
Connects React state to native auth. |
src/core/types.ts |
Defines domain types. |
src/core/store.ts |
Routes storage through authenticated requests. |
src/core/schemas.ts |
Validates native account state. |
src/core/preferences.ts |
Bridges native preferences. |
src/core/mapping.ts |
Maps API/domain models. |
src/core/list.ts |
Implements filtering and sorting. |
src/core/groups.ts |
Implements group counts. |
src/core/format.ts |
Provides display formatting. |
src/core/derive.ts |
Derives overview statistics. |
src/core/apiTypes.ts |
Defines Hominem API models. |
src/core/api.ts |
Implements bridged API calls. |
src/components/Sheet.tsx |
Removes the web sheet. |
src/components/Overview.tsx |
Removes web overview components. |
src/components/Menu.tsx |
Removes the web menu. |
src/components/ItemSheet.tsx |
Removes the web item form. |
src/components/ItemList.tsx |
Removes the web item list. |
src/components/GroupSheet.tsx |
Removes the web group form. |
src/components/Groups.tsx |
Removes web group components. |
src/auth.ts |
Removes browser authentication. |
src/App.tsx |
Removes the web application shell. |
src/api.ts |
Removes the fetch-based API client. |
scripts/ExportOptions-testflight.plist |
Configures App Store export. |
scripts/deploy-ios.sh |
Adds physical-device deployment. |
scripts/archive-testflight.sh |
Adds TestFlight archive/upload automation. |
README.md |
Documents iOS development and shipping. |
railway.json |
Removes Railway deployment. |
package.json |
Replaces web dependencies and scripts. |
nginx.conf |
Removes web server configuration. |
metro.config.js |
Configures Metro. |
jest.config.js |
Configures React Native tests. |
ios/Still/StillBridge.swift |
Bridges Swift services to JavaScript. |
ios/Still/StillBridge.m |
Exports bridge methods to React Native. |
ios/Still/PrivacyInfo.xcprivacy |
Declares privacy metadata. |
ios/Still/LaunchScreen.storyboard |
Adds the launch screen. |
ios/Still/Info.plist |
Configures the iOS application. |
ios/Still/Images.xcassets/Contents.json |
Defines the asset catalog. |
ios/Still/Images.xcassets/AppIcon.appiconset/Contents.json |
Defines app icons. |
ios/Still/AppDelegate.swift |
Boots React Native and Metro. |
ios/Still.xcworkspace/xcshareddata/swiftpm/Package.resolved |
Pins workspace Swift dependencies. |
ios/Still.xcworkspace/contents.xcworkspacedata |
Defines the Xcode workspace. |
ios/Still.xcodeproj/xcshareddata/xcschemes/Still.xcscheme |
Defines the shared Xcode scheme. |
ios/Still.xcodeproj/project.pbxproj |
Defines the iOS target and build. |
ios/Podfile |
Configures React Native pods. |
ios/.xcode.env |
Configures Node for Xcode. |
index.js |
Registers the React Native app. |
index.html |
Removes the web HTML entry point. |
docs/TESTFLIGHT.md |
Adds the TestFlight runbook. |
Dockerfile |
Removes containerized web builds. |
babel.config.js |
Configures React Native transforms. |
App.tsx |
Implements the native application shell. |
app.json |
Defines React Native app metadata. |
.watchmanconfig |
Configures Watchman. |
.gitignore |
Adds native build exclusions. |
.github/workflows/validate.yml |
Validates JavaScript and Swift. |
.env.example |
Removes web API configuration. |
.dockerignore |
Removes Docker exclusions. |
__tests__/store.test.tsx |
Tests store loading and mutations. |
__tests__/screens.test.tsx |
Tests native screen rendering. |
__tests__/preferences.test.ts |
Tests bridged preferences. |
__tests__/logic.test.ts |
Ports domain logic tests. |
__tests__/list.test.ts |
Tests filtering and sorting. |
__tests__/groups.test.ts |
Tests group counts. |
__tests__/api.test.ts |
Tests native API bridging. |
__tests__/account.test.tsx |
Tests account-state bridging. |
Files not reviewed (1)
- ios/Still.xcworkspace/contents.xcworkspacedata: Generated file
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| case let value as Bool: try container.encode(value) | ||
| case let value as Int: try container.encode(value) | ||
| case let value as Double: try container.encode(value) |
| STILL_METRO_PORT = 8084; | ||
| SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; | ||
| SWIFT_VERSION = 5.0; | ||
| TARGETED_DEVICE_FAMILY = "1,2"; |
| <BuildableReference | ||
| BuildableIdentifier = "primary" | ||
| BlueprintIdentifier = "00E356ED1AD99517003FC87E" | ||
| BuildableName = "StillTests.xctest" | ||
| BlueprintName = "StillTests" |
| public func requestCode(email: String) async { | ||
| let address = email.trimmingCharacters(in: .whitespacesAndNewlines) | ||
| guard address.contains("@"), address.contains("."), !state.busy else { | ||
| return update { $0.problem = .badEmail } | ||
| } |
| } catch { throw Self.problem(error) } | ||
| } | ||
|
|
||
| public func signOut() async { try? await auth.signOut() } |
| <FlatList | ||
| data={rows} | ||
| keyExtractor={(i) => i.id} | ||
| scrollEnabled={false} | ||
| renderItem={({ item }) => <Row item={item} containers={containers} actions={actions} />} |


Replaces the Still web SPA with a bare React Native iOS app (
io.ponti.still) backed by the StillCore Swift package.New
Removed
Validation (all green)
npx tsc --noEmit, jest 26/26,swift test15/15, actionlintBreaking change
The Still web app is removed. Rollback point: tag
still-web-final(last web commit). Railway shutdown stays manual post-merge.