- Create the screen component in
src/screens/YourScreen.tsxand wrap content withScreenContainerfor consistent layout (title/back button/offline-year banner). - Add a route name in
src/navigation/routes.tsfor the appropriate stack and update the matching*StackParamListtype. - Register the screen in
src/navigation/stackConfigs.tsunder the target stack with atitleoption. - Navigate to the new screen from existing UI using a typed
useNavigationhook, or a helper inuseAppNavigationif it needs to be reachable from multiple tabs/stacks.
If the new screen belongs in a new tab, also:
- Add a new stack entry in
stackConfigsand include the root screen. - Extend the
TabScreenConfigstackKeyunion insrc/navigation/stackConfigs.ts. - Add the tab to
TabRoutes/RootTabParamListinroutes.ts, and totabIconNames(Ionicons, web tab bar) andtabSfSymbols(SF Symbol name, native iOS tab bar) plustabScreensinstackConfigs.ts— the native and web tab bars are two different implementations (see Architecture) and both need the new tab's icon.
See docs/navigation.md for a full worked example.
- Start in
src/components/and choose the closest subfolder (for example,schedule/orsettings/). - Keep the component presentational; accept data and callbacks as props. (
ScreenContainerandOfflineNoticeare the deliberate exceptions that read app state directly — don't add more without good reason.) - Use theme values from
src/theme.ts/useAppThemerather than inline magic values. - If a new token is needed, add it in
src/theme.tsand wire it throughcreatePaperTheme. - Prefer small, composable components over large UI blocks embedded in screens — e.g.
ScheduleFilterscomposesScheduleDayChips/ScheduleTrackFilter/ScheduleLevelFilterrather than being one large file.
- Pure helpers belong in
src/utils/(formatting, time calculations, sorting, mapping helpers) — no React, no UI, side effects isolated to a single function where unavoidable. - Side-effectful flows that need React state belong in
src/hooks/. - Persistent user state should live in
src/store/and be exposed through a provider hook. - Data fetching and normalization for the conference-programme dataset belongs in
src/services/only.
- Keep route names centralized in
src/navigation/routes.tsand update the typed param lists together. - For destinations used across multiple stacks, add or extend a helper in
src/hooks/useAppNavigation.tsso cross-tab fallbacks stay consistent. If the destination is opened from a notification tap, also checksrc/hooks/useNotificationDeepLink.ts; if it's opened from aep{year}.europython.euuniversal link, also checksrc/hooks/useUrlDeepLink.ts— both are separate mechanisms fromuseAppNavigation. - Ensure screens using
useConferenceData,useSettings, oruseFavoritesremain inside their providers inApp.tsx(the onboarding stack is rendered beforeConferenceDataProvider/FavoritesProvidermount — don't call those hooks from onboarding screens). - When adding new settings, update default values, hydration parsing, and persistence together in
src/store/settings.tsx. - When changing schedule data shape, keep
ConferenceDatanormalization (src/services/conferenceTransform.ts) consistent withsrc/types/raw.ts/src/types/conference.ts, and bumpSCHEMA_VERSIONinsrc/config/conference.ts(see Data and state).
ios/ and android/ are gitignored — they're generated locally by Expo's prebuild ("Continuous Native Generation") the first time you run pnpm ios/pnpm android, not checked into the repo. If you need to change native configuration (permissions, icons, plugins), change app.config.js and re-run prebuild (expo prebuild --clean or just re-run pnpm ios/pnpm android) rather than hand-editing the generated native projects, since those edits won't survive a clean prebuild.
To re-run the first-run experience, go to Settings and use "Restart onboarding experience" (resetOnboardingSeen). The app switches back to the onboarding stack on the next render, since AppContent in App.tsx gates on onboardingSeen directly.