Startup and routing
The startup path is designed to avoid blocking the first frame while still making all required services available before the user starts a turn.
Entry point
main.dart wraps boot in runZonedGuarded. Native engine failures, worker isolate failures, and other uncaught async errors are logged in debug builds instead of taking the app down.
The _boot function performs the startup work:
- Calls
WidgetsFlutterBinding.ensureInitialized. - Enables edge-to-edge system UI.
- Loads SharedPreferences.
- Applies the global haptics setting before any provider-less tap can fire.
- Configures
background_downloadernotifications for model transfers. - Creates the production
FlutterGemmaService. - Starts
FlutterGemmaService.bootstrap()without awaiting it. - Creates
AgentServiceand starts loading bundled skills. - Runs the app inside a ProviderScope with service overrides.
The engine bootstrap is intentionally asynchronous. Awaiting it before runApp previously skipped frames at launch. Every engine entry point awaits the bootstrap future internally, so app startup and model readiness remain coordinated.
Provider overrides
The root ProviderScope injects concrete services:
sharedPreferencesProviderreceives the loaded SharedPreferences instance.gemmaModelServiceProviderreceivesFlutterGemmaService.memoryServiceProviderreceivesFlutterGemmaMemoryService.agentServiceProviderreceives the startup-createdAgentService.
This keeps controllers engine-agnostic and allows tests to substitute fake services.
App-level listeners
MuninApp is the root widget. It starts long-lived app listeners in initState:
- Incoming share intents start through
shareIntentServiceProvider. - Memory warms after the first frame through
memoryServiceProvider.warmUp(). - Device RAM is read through
DeviceActions.getSystemInfo(). - The saved performance mode is applied through
GemmaModelService.applyPerformance(). - Native assist events are received on
MethodChannel('munin/assist').
The assist channel handles two methods:
assist: pushes the voice route.runWorkspace: opens a workspace by id and runs it after loading workspace state.
First destination
The first visible page is the splash screen. SplashPage listens to appDestinationProvider, waits for a short minimum dwell, and routes to one of three destinations:
- Onboarding when onboarding is not complete.
- Model download when the required model is not installed.
- Chat when setup is complete.
This keeps redirect logic out of the router and makes startup decisions testable.
Route table
Routes are declared once in Routes and registered in app_router.dart. The router uses GoRouter with explicit route definitions and a shared fade transition helper.
The route list includes:
Routes.splashRoutes.onboardingRoutes.downloadRoutes.chatRoutes.settingsRoutes.memoryRoutes.mcpSettingsRoutes.skillsSettingsRoutes.aboutRoutes.voiceRoutes.workspacesRoutes.workspaceCreateRoutes.workspaces/:id
No route string is inlined at call sites. Pages navigate through Routes.
Native entry points
Android integration exposes additional entry paths:
- The manifest registers the app as an assist-capable activity.
- Wake-word and voice surfaces are backed by native services.
- Workspace scheduling can bring the user back to a workspace and ask the Flutter side to run it.
Native methods do not directly mutate app state. They signal Flutter through platform channels, and Flutter controllers perform the actual state transition.