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
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:
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
- 6 screens: login landing, email entry, password entry, forgot password, verification code, reset password
- Social login buttons (Apple, Google) with branded OAuth logos
- AuthController state machine manages all transitions
Onboarding
- Splash screen with configurable logo
- Naming step (name your assistant, with suggestions)
- Multi-select pills (pick interests or capabilities)
- Custom step support for app-specific screens
- Reveal animation to transition into the main app
- Set
onboardingConfig: nullto skip entirely
Chat Experience
- Markdown rendering with code blocks and copy button
- AI thinking panels (expandable reasoning display)
- Animated send button (mic/arrow morph based on input state)
- Attachment menu (camera, photo library, files) with thumbnail preview
- Voice input via speech-to-text
- Model selector bottom sheet (switch between AI models)
- Scroll-to-bottom FAB when scrolled up
- Regenerate response button on assistant messages
- Haptic feedback on key actions (send, new chat, voice toggle)
- Empty state with greeting when no conversation is active
Sidebar
- Conversation list with date grouping (Today, Yesterday, Previous 7 Days, etc.)
- Conversation actions: rename, star, share, delete
- Share conversation via system share sheet
- Settings sheet with configurable sections
- Search across conversations
- New chat button
Session Persistence
- Auth tokens stored in iOS Keychain / Android Keystore via
flutter_secure_storage - Automatic session restore on app launch (no re-login needed)
- Tokens are cleared on sign-out
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:
This command:
- Generates CMMD provider implementations (
CmmdAuthProvider,CmmdAiProvider,CmmdStorageProvider,CmmdVoiceProvider) - Adds backend dependencies (
http,sign_in_with_apple,google_sign_in,url_launcher,speech_to_text) - Rewrites
lib/main.dartto 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:
| Property | Type | Default | Description |
|---|---|---|---|
| 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:
- Unauthenticated users are redirected to
/auth - Newly authenticated users go to
/onboarding(if configured) or/ - Authenticated users with a restored session land on
/(home) directly
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',
),
],
),
],
),
),
),
);
}