This project uses Expo Router (file-based routing) and a small set of well-separated layers:
src/app/: route entry points (screens) + root layoutsrc/components/: reusable UI components (buttons, inputs, markdown renderer, native tabs wrapper, tab screen header, drawer content,auth/shared auth form layout, etc.)src/ctx/: app-wide React context (authentication/session, theme preference, chat actions for drawer → new chat, chat history for drawer list + apply session)src/services/: network + streaming logic for AI; encrypted local chat history (chat-history-storage.ts)src/types/: shared TS types (e.g. chat history store shape)src/utils/: helpers (AI credentials storage,session-account-storagefor per-user SecureStore key suffix,chat-launch-preferencefor Chat open behavior,chat-message-copy-preferencefor optional whole-message copy,chat-share-link/chat-deeplink-pendingforchatapp://chat/<id>share + post–sign-in open, env defaults, navigation theme, personalization,session-emailfor display labels, error mapping)src/constants/: small shared constants (e.g. theme preference helpers)src/hooks/: small state/hooks wrappers (theme + storage)
Key files:
src/app/_layout.tsx: root layout;ThemePreferenceProvider→SessionProvider→ChatHistoryProvider→ React NavigationThemeProviderwithcreateAppNavigationTheme()→ nativeStack(themed headers on auth/settings,headerShown: falseon the main drawer group). Includeschat/[sessionId](deep link handler) outside the signed-in-only stack group so links work before sign-in. Splash, toast, status bar.src/app/(main)/_layout.tsx: drawer layout (expo-router/drawer) wrapping the tab group; custom drawer content (src/components/main-drawer-content.tsx) includes New chat and History;ChatActionsProviderbridges drawer ↔ chat (ChatHistoryProviderlives at root).src/app/chat/[sessionId].tsx: resolveschatapp://chat/<id>(scheme fromapp.json); opens the thread from local encrypted history for the signed-in account, or stashes the id for after sign-in. Testing in Expo Go / simulator: seedocs/deep-links.md.src/app/(main)/(tabs)/_layout.tsx: tab navigator wrapper (Chat,Settings) viaNativeTabs(src/components/app-tabs.tsx).src/app/(main)/(tabs)/index.tsx: chat (streaming, composer + model strip, scroll/catch-up, day sections, timestamps). UsesTabScreenHeadertitle Chat + drawer control + share (deep link to this thread id).src/app/(main)/(tabs)/settings.tsx: settings (Appearance, Profile, AI, sign out, etc.). UsesTabScreenHeadertitle Settings + same drawer control.
Navigation-related components:
src/components/app-tabs.tsx—expo-router/unstable-native-tabs(NativeTabs) tab bar labels/icons for Chat & Settings.src/components/tab-screen-header.tsx— shared top bar for tab roots: centered screen title + ☰ (DrawerActions.openDrawer); optional share action on the right (Chat).src/components/main-drawer-content.tsx— drawer body: New chat + navigation back to Chat tab.src/ctx/chat-actions-context.tsx— registers the chat screen’s reset handler; supports pending new chat when Chat isn’t mounted.
Auth/settings routes:
src/app/(auth)/sign-in.tsxsrc/app/(auth)/sign-up.tsxsrc/app/(auth)/forgot-password.tsxsrc/app/(auth)/settings-profile.tsxsrc/app/(auth)/settings-security.tsxsrc/app/(auth)/settings-ai.tsxsrc/app/(auth)/change-password.tsx
streamChatCompletion() performs the OpenAI-compatible chat.completions.create({ stream: true }) call and yields content token deltas to the UI.
Supporting modules:
src/utils/chat-completion-history.ts— maps UI messages to officialopenaiChatCompletionMessageParam/ChatCompletionContentPart(attachments →image_url,filewithfile_data, ortext)src/utils/openai-chat-file-helpers.ts—toFilewrapper for optionalclient.files.create(seedocs/openai-sdk-file-support.md)src/utils/ai-credentials-storage.ts— per-account API key, base URL, model id (legacy SecureStore key ids kept for migration)src/utils/ai-api-key-env.ts— optionalEXPO_PUBLIC_*API key fallbacks
createAppNavigationTheme(resolvedScheme) builds the React Navigation Theme (including colors.card / text / primary) so the native stack header matches app light/dark. materialAndroidUiColors(isDark) is shared with useNativeThemeColors() for consistent Android palettes (see theming.md).
The app abstracts:
- Native secure storage via
expo-secure-store - Web storage via
localStorage
Storage key names are derived from:
- the current session/email
- a small set of fixed “global” keys for legacy migration.
src/constants/theme-preference.ts—resolveColorSchemeFromPreference, storage helpers for Appearancesrc/ctx/theme-preference-context.tsx— persisted system/light/dark +Appearance.setColorScheme(useLayoutEffect;'unspecified'for system on Android)src/hooks/use-native-theme-colors.ts— semantic UI colors (iOSColor.ios.*, AndroidmaterialAndroidUiColors, web hex)src/utils/navigation-theme.ts— navigationTheme+ Android palette helper
See theming.md.