System architecture for the Flutter client, Appwrite backend, and repository layering.
Ponder is a Flutter Material 3 application initialized in lib/main.dart and assembled by PonderApp in lib/app.dart. The app loads .env, initializes Sentry, optionally starts New Relic Mobile when an iOS or Android token is provided through .env or Dart defines, creates a Riverpod ProviderScope, and uses MaterialApp.router with the router from routerProvider.
The backend is Appwrite. The repository uses Appwrite Auth, TablesDB, Functions, and Storage. appwrite.json declares the backend contract and lib/core/constants/appwrite_config.dart declares the IDs consumed by Flutter.
| Layer | Code area | Responsibility |
| — | — | — |
| UI | lib/features/**/screens, lib/shared/widgets | Screens, user input, presentation, modal sheets, and navigation calls. |
| State | lib/features/**/providers, lib/core/providers | Riverpod providers/notifiers, async state, cache invalidation, local preference state. |
| Services and repositories | lib/core/services, lib/features/**/services | Business logic, Appwrite queries, function execution, profile image upload, encryption-aware chat orchestration. |
| Backend access | TablesService, Appwrite SDK providers | TablesDB REST calls, Account, Databases, Functions, Storage clients. |
| Backend runtime | functions/** | Scheduled content ingestion, fact-check execution, chat writes, like-count synchronization. |
Widgets do not contain direct database logic. The active pattern is widget -> provider/notifier -> service/repository -> TablesService or Appwrite SDK client.
lib/main.dart loads .env, initializes SentryFlutter, conditionally starts NewrelicMobile, and starts ProviderScope.lib/app.dart watches authProvider, feedPreferencesMigrationProvider, routerProvider, and themeModeProvider.lib/core/router/app_router.dart declares app routes, attaches NewRelicNavigationObserver to GoRouter, and defines the persistent ShellRoute.lib/core/shell/main_shell.dart owns bottom navigation and opens onboarding as a fullscreen dialog when local onboarding state requires it.lib/core/monitoring wraps New Relic behind a conditional adapter so non-mobile targets remain no-op safe. The app records auth context, route breadcrumbs, Appwrite table/function operation timings, critical-flow events, and selected handled errors without sending sensitive content such as email addresses, chat bodies, article bodies, fact-check claim text, print statements, or HTTP response bodies.
AppwriteConfig reads APPWRITE_ENDPOINT and APPWRITE_PROJECT_ID from .env; missing values throw a StateError. The database ID is ponder_db. Table IDs and function IDs are constants.
appwrite_service.dart exposes Riverpod providers for:
ClientAccountDatabasesFunctionsStorageTablesService wraps Appwrite TablesDB REST endpoints under /tablesdb/{databaseId}/tables/{tableId}/rows and exposes listRows, getRow, createRow, updateRow, and deleteRow.
article: article detail, fact-check shortcuts, saves, notes, sharing.fact_check: claim submission, daily limits, starter claims, result cards.for_you: personalized feed session, local/remote tuning, like UI.friends: user search, friendships, activity inbox, threads, shares.onboarding: first-run interest capture and feed-preference persistence.profile: auth-aware profile, edit profile, archive, saved items, liked items, fact-check history.search: unified search across content, fact checks, saved items, and notes.The active Appwrite backend declares:
ponder_db.users, content, saved_items, content_likes, user_fact_check_history, fact_checks, friendships, threads, thread_messages, and shares.profile_images.news_refresh, fact_check, chat_write, and like_sync.The Flutter and function code retain compatibility for older chat schemas. Threads prefer pairKey but still write contentId as dm:{sortedParticipantIds}. Thread messages prefer type and contentId but can encode article shares in the legacy body prefix __ponder_article__: when the deployed schema does not support typed columns.