All docs

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_downloader notifications for model transfers.
  • Creates the production FlutterGemmaService.
  • Starts FlutterGemmaService.bootstrap() without awaiting it.
  • Creates AgentService and 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:

  • sharedPreferencesProvider receives the loaded SharedPreferences instance.
  • gemmaModelServiceProvider receives FlutterGemmaService.
  • memoryServiceProvider receives FlutterGemmaMemoryService.
  • agentServiceProvider receives the startup-created AgentService.

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.splash
  • Routes.onboarding
  • Routes.download
  • Routes.chat
  • Routes.settings
  • Routes.memory
  • Routes.mcpSettings
  • Routes.skillsSettings
  • Routes.about
  • Routes.voice
  • Routes.workspaces
  • Routes.workspaceCreate
  • Routes.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.