Skip to content

Cross-Platform (Web + Mobile) ​

Gatherloop POS ships as a Next.js web app and a React Native mobile app from one shared codebase, libs/ui. This isn't a shared component kit that two separate products both import — the screens, business logic, and entities are identical, literal, single-source code running on both platforms. What differs between web and mobile is a thin shell of routing and initial data-loading, nothing more.

What's shared (nearly everything) ​

Every layer described in Clean Architecture — entities, usecases, controllers, handlers, and screens — lives in libs/ui and is imported as-is by both apps. Concretely, apps/pos-mobile/src/app/App.tsx and apps/pos-web/src/pages/** both import components straight from libs/ui/src/app/pos via the @gatherloop-pos/ui/pos entry point (e.g. TransactionList, ProductCreate, ChecklistSessionDetail) — the same composition root that instantiates the real API repositories, wires up the usecases, and renders the same Handler and Screen. libs/ui/src/app and libs/ui/src/presentation/screens each split into a pos/ and an order/ folder, so the customer ordering app (apps/order-web) draws from its own order/ slice instead.

That sharing is possible because of Tamagui: the Screen layer is written once using Tamagui's component set (View, Button, Text, …), which compiles to real DOM on web and real native views on iOS/Android — not a webview, and not two separate implementations kept in sync by hand.

What's platform-specific (the shell) ​

ConcernWeb (apps/pos-web)Mobile (apps/pos-mobile)
RoutingNext.js file-based pages under apps/pos-web/src/pagesReact Navigation's NativeStackNavigator, configured in App.tsx
URL structureNative to Next.jsA linking.config in the same App.tsx mirrors the exact same paths (transactions/:transactionId, wallets/:walletId/transfers, …), kept in sync by hand so a deep link means the same thing on either platform
Cross-platform link/route glueSolitoSolito
Initial datagetServerSideProps fetches data server-side with the real API repositories and passes it as props — faster first paint, and where the login redirect is enforcedScreens mount with empty initial params and the shared usecase fetches client-side on mount instead
Auth gateServer-side redirect in getServerSideProps if no session cookieClient-side, driven by the same AuthLogoutUsecase shared with web

Everything below the routing layer — what a "product create" screen looks like, what happens when a transaction is paid, how a budget total is recomputed — is the same code, not a parallel implementation.

Why this matters ​

A conventional "web + mobile" POS is really two products that happen to talk to the same API, with every screen, form, and business rule built and maintained twice — and two chances for the two to quietly disagree. Here, a bug fix or a new validation rule in a usecase is fixed everywhere at once, and a new feature built for web (like Board-game Rentals) is one Stack.Screen registration away from also working on mobile, because the screen, its state machine, and its data layer already exist.

Explore the source ​

Built with VitePress. Content lives in docs-site/ — see the README for how to run it locally.