Remove unnecessary conditional check that prevented navigation to home when app first loads. Expo Router defaults to the first alphabetically-ordered tab group (chats) before the navigation effect fires. Since the effect is guarded by hasNavigated, we can unconditionally navigate to home when the user has workspaces. Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Colanode Mobile
Status: Experimental – work in progress The Colanode mobile app is under active development and not ready for production use. It is included in this repository to make it easier to test, iterate, and contribute to its development.
Overview
The mobile app brings Colanode's local-first collaboration platform to iOS and Android. Built with Expo and React Native, it shares the same @colanode/client and @colanode/core packages used by the web and desktop apps, giving it offline-first operation, CRDT-based sync via Yjs, and a local SQLite database for instant reads.
What's implemented
- Authentication – Server selection, email login with OTP verification, registration, and password reset.
- Real-time chat – 1:1 and channel messaging with replies, editing, reactions (emoji picker with categories and search), copy, and delete.
- Spaces & content – Browse spaces, channels, pages, folders, and files. View rich text pages (paragraphs, task lists, tables, mentions) and preview/download files.
- Content creation – Create spaces, channels, pages, and folders. Upload files from the device. Rename and delete nodes.
- Workspace management – Switch between workspaces, create new ones, invite members, and manage roles.
- Settings – Account and workspace settings with avatar upload, members list, and app info.
- Offline awareness – Network status banner, unread counts from radar data, and pull-to-refresh throughout.
What's not included
- Rich text editing (messages are plain text only)
- Database views
- Push notifications
Getting started
Prerequisites
- Node.js (see root
.nvmrc) - npm (workspaces are managed at the monorepo root)
- Expo CLI (
npx expo) - Xcode (for iOS Simulator) or Android Studio (for Android Emulator)
- A running Colanode server (see
apps/server/README.md)
Install dependencies
From the repository root:
npm install
This also runs the postinstall script that generates emoji and icon assets required by the app.
Run on iOS Simulator
cd apps/mobile
npx expo run:ios
Run on Android Emulator
cd apps/mobile
npx expo run:android
Start the dev server only
cd apps/mobile
npx expo start
Architecture
Project structure
apps/mobile/
├── app/ # Expo Router file-based routes
│ ├── _layout.tsx # Root layout (AppService init, auth routing)
│ ├── (auth)/ # Auth screens (login, register, reset, etc.)
│ └── (app)/ # Main app (tab navigation)
│ ├── (home)/ # Home tab – unread summary, recent activity
│ ├── (spaces)/ # Spaces tab – browse and manage content
│ ├── (chats)/ # Chats tab – messaging
│ └── (settings)/ # Settings tab – account, workspace, members
├── src/
│ ├── components/ # Reusable React Native components
│ ├── contexts/ # React Context providers
│ ├── hooks/ # Custom hooks (queries, mutations, network)
│ ├── lib/ # Utilities (colors, crypto polyfill, etc.)
│ ├── services/ # Mobile-specific service implementations
│ └── mocks/ # Metro bundler mocks for DOM-only packages
├── index.js # Custom entry point (loads crypto polyfill first)
├── metro.config.js # Bundler config with module mocking
└── app.json # Expo configuration
How it works
The app follows the same local-first architecture as the web and desktop clients:
- All data reads come from a local SQLite database (instant).
- Writes are applied locally first, then synced to the server in the background.
- CRDT updates (Yjs) handle conflict resolution automatically.
- A WebSocket connection keeps the local database in sync with the server.
The entry point (index.js) loads a crypto.getRandomValues polyfill before anything else — this is required because Yjs and the ulid ID generator call it at module load time and React Native doesn't provide it natively.
Key layers
- Routing – Expo Router with
(auth)and(app)route groups. The app group uses tab navigation with nested stack navigators. - Contexts –
AppServiceContext(client singleton),WorkspaceContext(current userId/workspaceId/role),WorkspaceSwitcherContext, andThemeProvider. - Hooks –
useLiveQueryfor reactive subscribed queries,useQueryfor one-shot reads,useNodeListQueryfor node lists with eventBus invalidation, anduseMutationfor writes. - Services – Three mobile-specific implementations bridge
@colanode/clientinterfaces to Expo APIs:MobileFileSystem– file I/O viaexpo-file-systemMobilePathService– path resolution using Expo'sPathsAPIMobileKyselyService– SQLite viaexpo-sqlitewith a custom Kysely dialect
Metro bundler config
The metro.config.js customizes the bundler in two ways:
Asset extensions – .db files are registered as assets so the emoji and icon SQLite databases can be imported directly from assets/.
Module mocking – Some packages pulled in transitively by @colanode/client are DOM-only and would crash the React Native bundler. The config redirects them:
@tiptap/coreand@tiptap/pm→ empty module (rich text editor, not used on mobile)isomorphic-webcrypto→ custom mock that delegates toglobalThis.crypto(set up by the polyfill)