App Scaffold

Production app shell wiring auth, onboarding, chat, and sidebar flows with GoRouter. One command installs everything and generates a ready-to-run main.dart.

Installation

$ flai add app_scaffold

This single command installs 7 bricks, adds pub dependencies (go_router, flutter_secure_storage, share_plus, flutter_markdown, markdown, image_picker, file_picker), and auto-generates lib/main.dart.

What Gets Installed

Running flai add app_scaffold resolves the full dependency tree and installs 7 bricks in order:

auth_flow onboarding_flow chat_experience sidebar_nav message_bubble typing_indicator app_scaffold

The full dependency tree:

// app_scaffold
//   ├── auth_flow           (6 auth screens + AuthController)
//   ├── onboarding_flow     (splash, naming, multi-select, reveal)
//   ├── chat_experience     (composer v2, voice, model selector, attachments)
//   ├── sidebar_nav         (drawer, conversation list, settings sheet)
//   ├── message_bubble      (markdown rendering, code blocks, thinking panels)
//   ├── typing_indicator    (animated loading dots)
//   ├── go_router           (added to pubspec.yaml)
//   ├── flutter_secure_storage  (session persistence)
//   └── share_plus          (conversation sharing)

After installation, the CLI also generates lib/main.dart using values from your flai.yaml (app name, assistant name, theme). The app is ready to run immediately with MockAuthProvider.

What You Get

A complete, production-ready AI chat app with every screen and interaction wired together:

Auth Flow

Onboarding

Chat Experience

Sidebar

Session Persistence

Auto-Generated main.dart

When you run flai add app_scaffold, the CLI reads your flai.yaml and generates a lib/main.dart that is ready to run. It uses MockAuthProvider so you can explore the entire UI without any backend:

import 'package:flutter/material.dart';

import 'flai/app_scaffold.dart';
import 'flai/core/theme/flai_theme.dart';
import 'flai/flows/auth/mock_auth_provider.dart';
import 'flai/flows/chat/chat_experience_config.dart';
import 'flai/flows/sidebar/settings_config.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    FlaiApp(
      config: AppScaffoldConfig(
        appTitle: 'My App',
        authProvider: MockAuthProvider(),
        theme: FlaiThemeData.dark(),
        chatExperienceConfig: ChatExperienceConfig(
          assistantName: 'Assistant',
        ),
        settingsConfig: SettingsConfig(
          sections: [
            SettingsSection(
              title: 'Account',
              rows: [
                NavigationRow(
                  icon: Icons.logout,
                  label: 'Sign Out',
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

The appTitle, assistantName, and theme values come from whatever you configured in flai.yaml during flai init.

Connect a Backend

The auto-generated main.dart uses MockAuthProvider for instant prototyping. To connect a real backend, run:

$ flai connect cmmd

This command:

  1. Generates CMMD provider implementations (CmmdAuthProvider, CmmdAiProvider, CmmdStorageProvider, CmmdVoiceProvider)
  2. Adds backend dependencies (http, sign_in_with_apple, google_sign_in, url_launcher, speech_to_text)
  3. Rewrites lib/main.dart to use the real CMMD providers instead of mocks

After connecting, run flutter pub get then flutter run. You get authentication (email, Apple, Google), AI chat streaming, conversation persistence, and voice -- all wired to cmmd.ai.

Configuration

AppScaffoldConfig composes all provider and flow configurations into a single entry point:

PropertyTypeDefaultDescription
authProvider AuthProvider required Authentication provider for sign-in, sign-up, and session management. The only required field.
aiProvider AiProvider? null AI chat streaming provider. If null, the chat screen shows an "AI provider not configured" placeholder.
storageProvider StorageProvider? InMemoryStorageProvider Conversation persistence provider. Falls back to in-memory storage so the app works during development.
voiceProvider VoiceProvider? null Voice transcription and synthesis provider. If null, voice features are disabled regardless of ChatExperienceConfig.enableVoice.
theme FlaiThemeData? FlaiThemeData.dark() Theme data applied to the entire app. 4 built-in presets: dark(), light(), ios(), premium().
authFlowConfig AuthFlowConfig AuthFlowConfig() Configuration for auth flow screens (social providers, branding, legal links).
onboardingConfig OnboardingConfig? null Configuration for the post-auth onboarding flow. If null, onboarding is skipped and users go straight to the chat.
chatExperienceConfig ChatExperienceConfig ChatExperienceConfig() Configuration for the chat experience (assistant name, composer options, model selector, feature flags).
sidebarConfig SidebarConfig? null Configuration for the sidebar navigation drawer. If null, uses a minimal default with the app title.
settingsConfig SettingsConfig SettingsConfig() Configuration for the settings sheet (sections, toggles, info items).
appTitle String 'FlAI Chat' Application title shown in the OS task switcher.
showSplash bool true Whether to show a splash screen before the auth flow. Set to false to skip straight to login.

Architecture

FlaiApp is a StatefulWidget that sets up the entire app shell. On launch it attempts to restore a persisted auth session from secure storage so the user stays logged in between app restarts:

// FlaiApp (StatefulWidget — restores session, builds provider tree)
//   └── FlaiTheme (InheritedWidget — theme data)
//       └── FlaiProviders (InheritedWidget — 4 provider interfaces)
//           └── MaterialApp.router
//               └── GoRouter (AuthStateNotifier triggers redirects)
//                   ├── /splash      → SplashScreen
//                   ├── /auth        → AuthFlowScreen (6 screens via AuthController)
//                   ├── /onboarding  → OnboardingFlowScreen (step state machine)
//                   └── /            → HomeScreen (sidebar + chat content)

Auth state redirects are handled by AuthStateNotifier, which listens to AuthProvider.authStateChanges and triggers GoRouter redirects:

The home route creates a HomeController that bridges all 4 providers to the UI. It handles conversation loading, message sending/streaming, starring, renaming, sharing, and deletion -- the developer only provides the backend provider implementations.

Providers

FlaiProviders is an InheritedWidget that exposes all 4 provider interfaces from anywhere in the widget tree:

// Access providers from any widget
final providers = FlaiProviders.of(context);

providers.authProvider;     // AuthProvider
providers.aiProvider;       // AiProvider?
providers.storageProvider;  // StorageProvider
providers.voiceProvider;    // VoiceProvider?

Usage

The auto-generated main.dart is the recommended starting point. Customize it by adding providers, adjusting configs, or swapping the theme:

import 'package:flutter/material.dart';

import 'flai/app_scaffold.dart';
import 'flai/core/theme/flai_theme.dart';
import 'flai/flows/auth/mock_auth_provider.dart';
import 'flai/flows/chat/chat_experience_config.dart';
import 'flai/flows/sidebar/settings_config.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    FlaiApp(
      config: AppScaffoldConfig(
        appTitle: 'My App',
        authProvider: MockAuthProvider(),
        aiProvider: myAiProvider,       // add your AI provider
        storageProvider: myStorage,     // add your storage provider
        voiceProvider: myVoice,         // add your voice provider
        theme: FlaiThemeData.dark(),
        chatExperienceConfig: ChatExperienceConfig(
          assistantName: 'Atlas',
          availableModels: [
            ModelOption(
              id: 'claude-sonnet-4-20250514',
              name: 'Claude Sonnet',
              description: 'Fast and intelligent',
              icon: Icons.bolt_rounded,
            ),
          ],
        ),
        settingsConfig: SettingsConfig(
          sections: [
            SettingsSection(
              title: 'Account',
              rows: [
                NavigationRow(
                  icon: Icons.logout,
                  label: 'Sign Out',
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}