feat(redesign): ✨ Rebuild Loyus on Expo SDK 58 with store brands - #35
Merged
Merged
Conversation
Move the app to Expo SDK 58 (preview.7), React Native 0.88.0-rc.1 and React 19.3, and switch the package manager from pnpm to Yarn 4.18.1. SDK 58 builds with Xcode 27 without local Podfile patches. Package manager - Yarn 4 via Corepack with `nodeLinker: node-modules` (RN has no PnP) - Husky hook wired through `postinstall` (Yarn Berry skips `prepare`) - CI moved to Corepack + Node 24, `corepack: true` in EAS profiles Dependencies - Bump Nitro 0.37, MMKV 4.3.2, Unistyles 3.3, i18next 26.4, zustand 5.0.15 - Bump TypeScript 6.0, Biome 2.5, lint-staged 17; keep Jest 29 - Add @react-native/jest-preset (no longer shipped by react-native) - Drop @react-navigation/native, react-native-edge-to-edge, expo-modules-core, @shopify/flash-list, @expo-google-fonts/manrope VisionCamera 5 - Scanning split per platform: native object output on iOS, ML Kit on Android (excluded from iOS autolinking, no simulator slices) - Camera permission moved to app config (v5 ships no config plugin) Breaking API changes - useFocusEffect now imported from expo-router - InteractionManager replaced by requestIdleCallback - Strict RN types: TextInputInstance refs, ColorValue tab icons - Splash config moved to the expo-splash-screen plugin - File.write() awaited; compileSdk/targetSdk pins removed
Remove section markers, JSDoc that restates names, test narration, planning IDs and stale notes across app, src, mocks, configs and CI. The comments left are one-liners covering platform quirks, workarounds and constraints; empty catch blocks keep theirs for Biome. Comment-only change: code is identical to the previous commit.
498 retailers with names, aliases, markets, loyalty programs, popularity and colors in assets/brands/brands.json, and 442 SVG logos. Sources and licences are listed in docs/BRANDS.md.
- Native tabs (Cards, Settings, Search) with titles aligned to their actions, Liquid Glass buttons on iOS 26+, routes moved to src/app - Neutral UI on the system font; color comes from the card tiles, with six accent colors and a faint top gradient - Checkout: barcodes sized to the GS1 specs and snapped to device pixels, full brightness, rotation, note, and the store logo in the header - Cards: pinning, sorting (most used, recent, A to Z, manual reorder), owner, note, photos, duplicate check, reading a barcode from a photo - Store brands: 434 brands with their SVG logos and colors, a store row and a searchable sheet in the card form, typeahead suggestions - iOS widgets, quick actions, backup restore, iOS 27 search field fixes - No code comments; Biome and TypeScript clean, 422 Jest tests
Features, compatibility, setup, how the barcode sizing works, troubleshooting and roadmap, with ten screens in light and dark mode under docs/screenshots.
Nothing imported src/infra/network/netinfo.ts, so drop it together with the @react-native-community/netinfo dependency, its Jest mock, the EXPO_PUBLIC_E2E_OFFLINE flag and the notes that still described it.
- Backups: validate the type of every card field on restore (bad name, code, format or dates reject the file, malformed optional fields are dropped) and always delete the temporary export/import file, even when sharing fails or the file is invalid - Formats: add generic ITF (any even number of digits) instead of forcing ITF-14, accept Code 39 Full ASCII values and draw them with their shift pairs, accept GS1 DataBar Expanded payloads - Scanning: confirm a code once it stays in view for 500 ms, whatever its position in the frame, and forget codes lost for over a second - Form: the preview and the auto color follow the store linked from the typed name, saving waits for a photo still being copied and runs once - Photos: the card store deletes the photos of a removed card and the ones an edit drops - Settings: ignore unknown values read back from storage - Haptics: remove the doubled selection feedback on store picks and on the Android action sheet
- Share one copy of the card subtitle, the section titles, saving a new card, the image picker, the no-op catch handler, the card ratio and the thumbnail radius (new radius.thumb token) - Keep the language list in src/domain/language.ts, so settingsStore no longer imports infra/i18n and the three lists cannot drift - Fold accents in one place (foldText) for card and store search, and cache the normalized store names instead of redoing it per keystroke - Keep the search query in the search screen instead of uiStore, and wrap the tab gradients with withUnistyles instead of useUnistyles - Read Reduce Motion through one shared subscription - Remove dead code: parseBarcodeFormat, selectCardCount, IconKey, two unused test IDs and five unused strings - Check in architecture.test.ts that infra never imports state or the UI, and that state only uses infra/persistence
The Expo SDK 58 preview headers (expo-modules-jsi RuntimeScheduler.h) no longer compile with Xcode 26.2, which failed the e2e-ios job before Maestro ran. Use the xcode-27 image and its iPhone 17 simulator, and update the prerequisite in the README.
expo run:ios starts Metro by default, even for a Release build, then keeps streaming the app logs, so the step never ended and the job hit its 90-minute timeout before Maestro ran. The Release build embeds its bundle, so pass --no-bundler and let the command exit after launch.
Arthurmtro
previously approved these changes
Sep 30, 2026
Arthurmtro
left a comment
Contributor
There was a problem hiding this comment.
banger de zinzin en vrai!
With --device, expo run:ios also lists physical devices through devicectl, whose 5-second timeout expired on the runner and failed the step. The job already boots the simulator, and without --device the CLI picks the booted one through simctl only.
Maestro text selectors are regexes matched against the full element text, so "No card matches", "Brightness at maximum" and "This code looks temporary" never matched the longer strings the app shows.
actions/cache only saves in its post step when the job succeeds, so every failing run started from a cold 45-minute build. Restore and save the CocoaPods and DerivedData caches explicitly, saving right after the build step even on failure. Stop caching ios/Pods: ios/ is generated by prebuild, and a restored ios/Pods folder alone could make run:ios skip it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/DESIGN_RESEARCH.md: native tabs, a neutral UI where color comes from the cards, checkout barcodes sized to the GS1 specs, pinning, sorting and manual order, photos, notes, iOS widgets and quick actions.Test plan
yarn check,yarn typecheckandyarn test(422 tests) pass locallyNotes for reviewers
docs/BRANDS.md). This needs a decision before a store release;yarn brands:build --exclude-source=brandsoftheworldbuilds a catalog without them.UISearchTabyet (feat(iOS, Tabs): UseUISearchTabwithautomaticallyActivatesSearchprop software-mansion/react-native-screens#4679).ExpoFabricView.injectInitializer). Relaunching fixes it.