AppFlowy is an open-source alternative to Notion, delivering a cross-platform productivity suite across macOS, Windows, Linux, iOS, Android, and Web. The application follows a local-first architecture with optional cloud synchronization, built on a Flutter (Dart) frontend communicating with a Rust backend through FFI and Protobuf-encoded events. This guide covers the complete internal architecture, every Rust crate, the Flutter layer, the build system, collaboration engine, and operational concerns at the depth required to maintain, extend, or replicate the system.
Note
This guide documents AppFlowy version 0.11.4 at commit
4af02cd(2026-03-20). All code paths, struct names, event enums, and configuration keys are verified against that specific revision. See the User Guide for installation instructions, everyday usage, and feature walkthroughs.
1. Architecture Overview
1.1 Layered System Design
AppFlowy uses a strict two-layer architecture that separates UI rendering from business logic:
┌─────────────────────────────────────────────────────────┐
│ Flutter UI Layer │
│ (Dart: widgets, BLoCs, navigation, theming, plugins) │
├─────────────────────────────────────────────────────────┤
│ dart-ffi Bridge (Protobuf) │
│ AppFlowyDartConfiguration / AppFlowyYamlConfiguration │
├─────────────────────────────────────────────────────────┤
│ Rust Core Layer │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ flowy-core │ │ flowy-user │ │ flowy-folder │ │
│ │ (orchestrator)│ │ (auth/profile)│ │ (views/spaces) │ │
│ └──────┬───────┘ └──────────────┘ └────────────────┘ │
│ ┌──────┴───────┐ ┌──────────────┐ ┌────────────────┐ │
│ │flowy-document │ │flowy-database2│ │ flowy-ai │ │
│ │ (block docs) │ │(grid/board/cal)│ │(chat/complete)│ │
│ └──────────────┘ └──────────────┘ └────────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ flowy-search │ │flowy-storage │ │ flowy-server │ │
│ │ (Tantivy) │ │ (file upload) │ │(cloud abstraction)│
│ └──────────────┘ └──────────────┘ └────────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │collab-integrate│ │ lib-dispatch │ │ lib-infra │ │
│ │(CRDT builder) │ │(event system)│ │ (utilities) │ │
│ └──────────────┘ └──────────────┘ └────────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ lib-log │ │ build-tool │ │
│ │ (logging) │ │ (codegen) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
The Flutter layer handles all rendering, user interaction, and platform-specific concerns. The Rust layer handles all data operations, persistence, collaboration, synchronization, search indexing, and AI integration. Communication between them is exclusively through the lib-dispatch event system using Protobuf-serialized messages over FFI.
1.2 Core Design Principles
| Principle | Implementation |
|---|---|
| Local-first | All data stored locally in SQLite + CollabDB before any cloud sync |
| CRDT-based collaboration | The collab crate family provides conflict-free replicated data types for all document and database operations |
| Event-driven architecture | lib-dispatch routes typed events between Dart and Rust, with each domain crate registering its own event handlers |
| Plugin-based extensibility | Both the Flutter and Rust layers use plugin systems for registering new view types and capabilities |
| Offline-capable | Full functionality without network; cloud sync is additive |
| Type-safe FFI | Protobuf schemas define the contract between Dart and Rust, with code generation ensuring compile-time safety |
1.3 Data Flow Architecture
User Action (Flutter Widget)
│
▼
BLoC / Application Layer (Dart)
│
▼
Event Dispatch (Dart → FFI → Rust)
│ Protobuf-encoded AFPluginEventRequest
▼
lib-dispatch Router (Rust)
│ Matches event ID → handler function
▼
Domain Handler (e.g., flowy-folder event_handler.rs)
│
├──▶ Local Storage (SQLite / CollabDB)
│
├──▶ Collab Object (CRDT operations)
│ │
│ └──▶ Cloud Sync (WebSocket → AppFlowy Cloud)
│
└──▶ Response (Protobuf-encoded AFPluginEventResponse)
│
▼
Flutter Callback / Stream
1.4 Application Lifecycle
The AppFlowyCore struct in flowy-core/src is the main orchestrator. It holds Arc references to every manager and initializes them in a specific order. The AppLifeCycleImpl struct implements the AppLifeCycle trait from flowy-user, responding to authentication events:
On launch (already authenticated):
ServerProvider— configure sync and encryption fromUserCloudConfigFolderManager.initialize()— load workspace/view tree from local diskDatabaseManager.initialize()— prepare database editor cacheDocumentManager.initialize()— register document handlersFullIndexedDataProvider— start background indexingInstantIndexedDataProvider— start live indexingSearchManager— create Tantivy state, initialize search indexServerProvider.on_launch_if_authenticated()— establish cloud connectionAIManager.on_launch_if_authenticated()— initialize AI features
On sign in:
Same sequence with initialize_after_sign_in() variants that may fetch folder data from the cloud if not present on disk. The folder_init_data_source() method checks whether collab data exists locally via CollabKVAction::is_exist() before falling back to cloud fetch.
On sign out:
All managers are deinitialized. The Tantivy state is closed via close_document_tantivy_state(). Local data is preserved for the next sign-in.
On workspace switch:
The open_workspace lifecycle event reinitializes all managers for the new workspace context. Database and document managers get fresh initialization; the search index is rebuilt for the new workspace.
Important
Manager initialization order matters.
FolderManagermust initialize beforeDatabaseManagerbecause database views are children of folder views.SearchManagerrequires the Tantivy state created during folder initialization. Changing this order will cause runtime panics fromNoneunwraps on uninitializedWeakreferences.
2. System Requirements
2.1 Build Environment
| Component | Requirement |
|---|---|
| Flutter SDK | Stable channel (version compatible with Dart 3.x) |
| Rust toolchain | Stable (with nightly for some codegen features) |
| cargo-make | Required for the build system (cargo install cargo-make) |
| Protobuf compiler | protoc must be on PATH for code generation |
| Python 3 | Used by some build scripts |
| Git | For dependency fetching and version stamping |
2.2 Platform-Specific Requirements
macOS:
| Item | Value |
|---|---|
| Deployment target | macOS 11.0 (Big Sur) minimum |
| Xcode | Latest stable recommended |
| Architecture | Both arm64 (Apple Silicon) and x86_64 supported |
Windows:
| Item | Value |
|---|---|
| Architecture | x86_64 only (ARM64 not yet supported — see feature request #6403) |
| Visual Studio | Build Tools with C++ workload |
| CMake | Required for native library compilation |
Linux:
| Item | Value |
|---|---|
| Architecture | x86_64 and aarch64 |
| Shared libraries | libvdpau.so.1 may be required (issue #8655) |
| Display server | X11 or Wayland |
Android:
| Item | Value |
|---|---|
| compileSdk | 35 |
| minSdk | 29 (Android 10) |
| targetSdk | 35 |
| NDK version | 24.0.8215888 |
| ABI filters | arm64-v8a only |
| CMake | Required for native libs (CMakeLists.txt in android/app/src/main/) |
iOS:
| Item | Value |
|---|---|
| Deployment target | Defined in Xcode project |
| CI | Codemagic with mac_mini_m2 instances |
Web:
| Item | Value |
|---|---|
| Build target | Flutter Web with custom index.html |
| Rust compilation | Via wasm-pack or wasm-bindgen (Tauri integration available) |
2.3 Runtime Requirements
| Component | Purpose |
|---|---|
| SQLite | Local data persistence (via flowy-sqlite with WAL mode) |
| Tantivy | Full-text search indexing (embedded, no external dependency) |
| Ollama (optional) | Local AI model inference |
| AppFlowy Cloud (optional) | Sync, collaboration, cloud storage |
Tip
AppFlowy operates fully offline. Cloud sync, AI features, and online collaboration are all opt-in. A clean local installation requires only the application binary and sufficient disk space for SQLite databases and the Tantivy search index.
3. Configuration Reference
3.1 Application Configuration (AppFlowyCoreConfig)
The core configuration struct is defined in flowy-core/src and passed during application startup:
| Field | Type | Purpose |
|---|---|---|
name |
String |
Application name (default: "AppFlowy") |
device_id |
String |
Unique device identifier for sync differentiation |
cloud_config |
Option<AFCloudConfiguration> |
Cloud connection parameters |
app_version |
String |
Application version string ("0.11.4") |
3.2 Dart Configuration (AppFlowyDartConfiguration)
Defined in dart-ffi/src, this struct serializes the Dart-side environment config into the Rust layer via FFI:
| Field | Purpose |
|---|---|
root |
Application data root directory |
app_version |
Version string |
custom_app_path |
Optional custom application path |
origin_app_path |
Original application path |
device_id |
Device identifier |
cloud_type |
Server type enum value |
authenticator_type |
Authentication provider type |
The AppFlowyYamlConfiguration variant provides YAML-based configuration for environments that prefer file-based config over programmatic initialization.
3.3 Environment Variables
| Variable | Default | Purpose |
|---|---|---|
RUST_LOG |
"info" |
Rust logging level (standard tracing filter syntax: error, warn, info, debug, trace; supports per-module filters like flowy_folder=debug,flowy_user=info) |
DISABLE_EVENT_LOG |
unset | When set, suppresses event dispatch logging |
APP_VERSION |
"0.11.4" |
Application version override |
3.4 Cloud Configuration
Cloud settings are managed through UserCloudConfig in flowy-user-pub:
| Setting | Purpose |
|---|---|
enable_sync |
Master toggle for cloud synchronization |
enable_encrypt |
Enable end-to-end encryption for collab data |
encrypt_secret |
Encryption secret for E2E encrypted workspaces |
These are persisted per-user and loaded during the authentication lifecycle.
3.5 Testing Environment Variables
| Variable | Purpose |
|---|---|
af_cloud_test_base_url |
AppFlowy Cloud base URL for integration tests |
af_cloud_test_ws_url |
WebSocket URL for test cloud connections |
af_cloud_test_gotrue_url |
GoTrue authentication URL for tests |
3.6 Flutter SharedPreferences Keys
The kv_keys.dart file in lib/core/config/ defines all persistent preference keys used by the Flutter layer. The KeyValueStorage interface in kv.dart wraps SharedPreferences for cross-platform key-value storage. These preferences control UI state, theme selection, sidebar width, last-opened view, notification settings, and feature flags.
4. Deployment & Scaling
4.1 Build System
AppFlowy uses cargo-make (Makefile.toml) as its primary build orchestration tool. The build system spans Rust compilation, Flutter builds, Protobuf code generation, and platform-specific packaging.
Build Profiles
| Profile | Platform | Use Case |
|---|---|---|
development-mac-arm64 |
macOS Apple Silicon | Local development |
development-mac-x86_64 |
macOS Intel | Local development |
production-mac-arm64 |
macOS Apple Silicon | Release builds |
development-windows-x86 |
Windows x64 | Local development |
production-windows-x86 |
Windows x64 | Release builds |
development-linux-x86_64 |
Linux x64 | Local development |
production-linux-x86_64 |
Linux x64 | Release builds |
development-linux-aarch64 |
Linux ARM64 | Local development |
production-linux-aarch64 |
Linux ARM64 | Release builds |
Build Variables
| Variable | Value | Purpose |
|---|---|---|
APPFLOWY_VERSION |
"0.11.4" |
Version stamped into binaries |
PRODUCT_NAME |
"AppFlowy" |
Product identifier |
MACOSX_DEPLOYMENT_TARGET |
"11.0" |
Minimum macOS version |
CARGO_MAKE_CRATE_FS_NAME |
"dart_ffi" |
FFI crate name for build targets |
Included Build Scripts
The scripts/makefile/ directory contains modular build definitions:
| Script | Purpose |
|---|---|
desktop.toml |
Desktop platform build tasks |
mobile.toml |
iOS and Android build tasks |
protobuf.toml |
Protobuf compilation and Dart/Rust codegen |
tests.toml |
Test runner configurations |
docker.toml |
Docker-based build environments |
env.toml |
Environment setup and validation |
flutter.toml |
Flutter-specific build tasks |
tool.toml |
Utility build tasks |
tauri.toml |
Tauri (desktop web) build tasks |
web.toml |
Web platform build tasks |
4.2 Build Pipeline
A typical development build follows this sequence:
cargo make --profile development-mac-arm64 appflowy-dev
This triggers:
- Environment validation — checks for Flutter SDK, Rust toolchain, Protobuf compiler
- Protobuf generation — compiles
.protofiles, generates Dart and Rust bindings - Rust compilation — builds
dart_ffidynamic library (.dylib/.dll/.so) - Flutter build — compiles Dart code, links native library
- Asset bundling — fonts, icons, templates, translations
4.3 Protobuf Code Generation
The build-tool directory contains three crates that handle code generation:
| Crate | Purpose |
|---|---|
flowy-ast |
AST extraction from Rust structs (derives, attributes) |
flowy-codegen |
Protobuf file generation from Rust type definitions |
flowy-derive |
Procedural macros for ProtoBuf, ProtoBuf_Enum derives |
The generation flow:
- Rust structs annotated with
#[derive(ProtoBuf)]are parsed byflowy-ast flowy-codegengenerates.protofiles from the ASTprotoccompiles.prototo Dart and Rust bindings- Event maps are generated linking Dart event classes to Rust handler functions
Warning
After modifying any Protobuf-annotated struct, you must re-run the code generation step. Failing to do so causes deserialization errors at runtime because the Dart and Rust sides will have mismatched schemas. The error manifests as
ProtobufErrorin the dispatch layer.
4.4 Docker Deployment
Docker build files are available in frontend/scripts/docker-buildfiles/ for containerized builds. These are primarily used for CI/CD pipelines and reproducible release builds.
4.5 CI/CD
| Platform | Configuration |
|---|---|
| Codemagic | iOS workflow with mac_mini_m2 instances |
| Commit lint | Conventional commits enforced: build, chore, ci, docs, feat, feature, fix, refactor, style, test |
4.6 Android Signing
The Android build supports release signing via key.properties:
keyAlias=<alias>
keyPassword=<password>
storeFile=<path-to-keystore>
storePassword=<password>
Place this file at frontend/appflowy_flutter/android/key.properties. The current release build configuration falls back to debug signing when key.properties is absent.
5. Operations
5.1 Data Storage Layout
AppFlowy stores all user data locally. The storage hierarchy per user:
<app_data_root>/
├── <user_id>/
│ ├── collab_db/ # RocksDB-based CollabDB (CRDT state)
│ ├── sqlite/ # SQLite databases
│ │ ├── flowy-user.db # User profile, sessions, workspace list
│ │ ├── flowy-folder.db # Workspace structure, view metadata
│ │ └── flowy-chat.db # AI chat message history
│ ├── cache/ # File upload cache, temp storage
│ └── search_index/ # Tantivy index files per workspace
└── config/
└── device_id # Persistent device identifier
5.2 SQLite Configuration
The flowy-sqlite crate manages SQLite connections with these settings:
| Setting | Value | Purpose |
|---|---|---|
| Journal mode | WAL (Write-Ahead Logging) | Concurrent reads during writes |
| Foreign keys | Enabled | Referential integrity |
| Schema versioning | Migration-based | schema_version tracked per database |
| Connection pool | Single-writer, multiple-reader | Thread safety via Rust's ownership |
The flowy-sqlite-vec crate extends SQLite with vector operations for AI embedding storage, using the sqlite-vec extension with its own migration system (migrations/001-init/).
5.3 User Migrations
The flowy-user/src/migrations/ directory contains 9 sequential migration files that handle schema evolution across AppFlowy versions. Migrations run automatically on startup when a version mismatch is detected. Each migration is a Rust module implementing a migration trait with up() and down() methods.
Caution
User migrations execute during the authentication lifecycle, before managers are fully initialized. A failed migration leaves the user in an inconsistent state. The migration system does not support partial rollbacks — if a migration fails mid-execution, manual database repair may be required. Always back up the
collab_db/andsqlite/directories before upgrading to a new AppFlowy version in development.
5.4 Backup and Recovery
AppFlowy's local-first design means the user's data directory is the single source of truth for offline data. To back up:
- Close AppFlowy to ensure SQLite WAL is checkpointed
- Copy the entire
<user_id>/directory - For cloud-synced workspaces, the cloud retains a server-side copy of all collab objects
Recovery: replace the <user_id>/ directory with the backup copy and restart AppFlowy. The application will reconcile local state with the cloud on next sync (if enabled).
5.5 Job Queue and Background Tasks
The Rust layer uses AFPluginRuntime (a Tokio runtime wrapper) for async task execution. Background tasks include:
| Task | Owner | Behavior |
|---|---|---|
| Full indexed data provider | AppLifeCycleImpl |
Background scan of all workspace content for search indexing |
| Instant indexed data provider | AppLifeCycleImpl |
Real-time indexing of content changes |
| File upload queue | StorageManager |
Chunked file uploads with retry logic |
| Cloud sync | ServerProvider |
WebSocket-based real-time sync |
| AI model management | LocalAIController |
Ollama process management |
5.6 Logging
Logging uses the Rust tracing crate with the lib-log wrapper:
- Log level controlled by
RUST_LOGenvironment variable - Event dispatch logging can be disabled via
DISABLE_EVENT_LOG - Each major operation is instrumented with
#[instrument(skip_all)]for span-based tracing - Flutter-side logging uses standard Dart logging mechanisms
5.7 Health Monitoring
There is no built-in health endpoint for the desktop application. Cloud-connected instances report health through the client_api connection status. Monitor these signals:
- WebSocket connection state (connected, disconnected, reconnecting)
- Sync queue depth (pending collab updates)
- File upload queue length
- Search index freshness (last indexed timestamp vs. last modification)
6. Performance
6.1 Memory Architecture
AppFlowy uses Rust's ownership model to control memory lifecycle:
- Weak references (
Weak<T>) between managers prevent circular reference leaks.AppLifeCycleImplholdsWeakpointers to all managers — theArcowners live inAppFlowyCore. - Collab objects are opened on demand and cached in manager-level
DashMaporHashMapcaches. Closing a document/database drops the collab object and frees its CRDT state. - Search index (Tantivy) uses memory-mapped files for the index segments, keeping memory usage proportional to index size rather than total document count.
- File upload cache stores chunks in a temporary directory that is cleaned after successful upload.
6.2 Concurrency Model
| Layer | Model |
|---|---|
| Flutter UI | Single-threaded event loop (Dart isolate) |
| FFI bridge | Synchronous call from Dart, dispatched to Tokio runtime on Rust side |
| Rust event handlers | Async (tokio) with AFPluginRuntime managing the thread pool |
| SQLite | Single-writer via flowy-sqlite connection management |
| CollabDB | RocksDB with column families, concurrent reads supported |
| Search indexing | Background Tokio task with channel-based communication |
6.3 CRDT Performance Characteristics
The collab crate uses Yjs-compatible CRDTs (via the yrs Rust library). Performance implications:
| Operation | Complexity | Notes |
|---|---|---|
| Local insert | O(1) amortized | Appends to the operation log |
| Remote merge | O(n) where n = pending ops | Processes all pending remote operations |
| Snapshot creation | O(document size) | Full state serialization for persistence |
| History traversal | O(history length) | Undo/redo walks the operation log |
For large documents (>10,000 blocks) or databases (>10,000 rows), CRDT merge operations can cause noticeable latency during sync. The collab layer batches operations to mitigate this.
6.4 Search Performance
Tantivy provides sub-millisecond search for workspaces with typical content volumes (hundreds to low thousands of documents). The search manager maintains per-workspace indexes:
- Index creation: happens once per workspace on first access
- Incremental updates: document changes trigger index updates via the indexed data providers
- Memory: uses mmap, so OS page cache manages memory efficiently
- Disk: index size is typically 10-30% of the raw text content size
6.5 Network Optimization
Cloud sync uses WebSocket for real-time collab updates with these optimizations:
- Delta encoding: only changed operations are transmitted, not full document state
- Batched updates: rapid local edits are batched before transmission
- Compression: collab updates are compressed before network transfer
- Reconnection: automatic reconnection with exponential backoff on disconnect
7. Security
7.1 Authentication Architecture
AppFlowy supports multiple authentication backends through the AuthService trait pattern:
| Backend | Implementation | Location |
|---|---|---|
| AppFlowy Cloud | CloudAuthService |
lib/user/application/auth/ |
| Backend | BackendAuthService |
Direct server authentication |
| Mock | MockAuthService |
Testing only |
The Rust-side authentication lives in flowy-user, handling:
sign_in_with_email_password— email/password authenticationsign_up— new account creationsign_out— session termination and manager cleanupdelete_account— permanent account removalget_user_profile/update_user_profile— profile CRUD
User sessions are persisted locally in SQLite. The UserManager maintains the current session state and triggers lifecycle events on authentication state changes.
7.2 Cloud Authentication (GoTrue)
When connected to AppFlowy Cloud, authentication goes through GoTrue (the authentication service used by Supabase-compatible backends):
- User provides credentials (email/password, OAuth, magic link)
- GoTrue issues JWT tokens (access + refresh)
client_apistores tokens and refreshes them automatically- All API requests include the JWT in the Authorization header
- WebSocket connections authenticate with the same JWT
7.3 Encryption
| Layer | Mechanism |
|---|---|
| Transport | TLS for all cloud API and WebSocket connections |
| At rest (optional) | E2E encryption for collab data when enable_encrypt is true in UserCloudConfig |
| Encryption secret | Per-workspace secret stored in encrypt_secret field |
| Local data | Not encrypted at rest by default (relies on OS-level disk encryption) |
Warning
When E2E encryption is enabled (
enable_encrypt: true), theencrypt_secretis the only key that can decrypt the workspace data. If this secret is lost, cloud-stored collab data becomes permanently unrecoverable. AppFlowy does not implement a key recovery mechanism. Users must be warned to securely store their encryption secrets.
7.4 Data Isolation
- User isolation: Each user has a separate data directory keyed by
user_id - Workspace isolation: Collab objects are scoped by
workspace_id; the CollabDB uses(user_id, workspace_id, object_id)as the composite key - Search isolation: Tantivy indexes are per-workspace, preventing cross-workspace data leakage in search results
7.5 Input Validation
The lib-dispatch layer enforces input validation through the validator crate:
AFPluginData<T>wraps incoming Protobuf payloads- Structs implementing
validator::Validateare automatically validated on deserialization AFPluginDataValidatortrait provides the validation interface- Invalid inputs return
DispatchErrorbefore reaching business logic
The flowy-folder/src/entities/parser/ directory contains parser modules for workspace names, view names, and other user-provided identifiers, enforcing length limits and character restrictions.
7.6 Error Handling
The flowy-error crate defines the ErrorCode enum, which is the application's canonical error type. Key security-relevant error codes:
| Error Code | Meaning |
|---|---|
UserUnauthorized |
Authentication required or session expired |
RecordNotFound |
Requested resource does not exist (prevents enumeration) |
InvalidData |
Input validation failure |
LocalVersionNotSupport |
Data format version incompatible |
FileLimitExceeded |
Total file storage quota exceeded |
SingleFileLimitExceeded |
Individual file size limit exceeded |
FlowyError wraps ErrorCode with context messages and propagates through the event dispatch system back to the Dart layer.
7.7 Dependency Security
AppFlowy's Rust dependencies are managed through Cargo with Cargo.lock pinning exact versions. The client_api dependency connects to AppFlowy Cloud and is the primary network-facing component. No native code dependencies are loaded dynamically — all Rust code is statically linked into the FFI library.
8. Developer Internals
8.1 Event Dispatch System (lib-dispatch)
The event dispatch system is AppFlowy's central nervous system — the mechanism by which the Flutter UI communicates with Rust business logic. Understanding this system is prerequisite to working on any feature.
Core Types
| Type | File | Purpose |
|---|---|---|
AFPlugin |
module.rs |
Plugin registration: maps event IDs to handler functions |
AFPluginEventRequest |
request.rs |
Inbound event with payload and metadata |
AFPluginEventResponse |
response.rs |
Outbound response with status and data |
AFPluginData<T> |
data.rs |
Type-safe wrapper for Protobuf payloads |
AFPluginState |
module.rs |
Dependency injection: provides weak references to managers |
AFPluginRuntime |
runtime.rs |
Tokio runtime wrapper for async task execution |
Payload |
request.rs |
Either None or Bytes — the raw event payload |
ResponseBuilder |
response.rs |
Builder pattern for constructing responses |
DispatchError |
errors/ |
Error type for dispatch-layer failures |
Plugin Registration
Each domain crate registers its events through an event_map.rs file that returns an AFPlugin:
// Example: flowy-folder/src/event_map.rs
pub fn init(folder_manager: Weak<FolderManager>) -> AFPlugin {
AFPlugin::new()
.name("Flowy-Folder")
.state(folder_manager)
.event(FolderEvent::CreateView, create_view_handler)
.event(FolderEvent::ReadView, read_view_handler)
.event(FolderEvent::UpdateView, update_view_handler)
.event(FolderEvent::DeleteView, delete_view_handler)
// ... more events
}
The flowy-core orchestrator collects all plugins during initialization and registers them with the dispatch runtime.
Serialization Protocol
All data crossing the FFI boundary uses Protobuf:
- Dart creates a Protobuf message (e.g.,
CreateViewPayloadPB) - Serializes to bytes
- Calls FFI function with event ID + bytes
- Rust
lib-dispatchroutes to handler based on event ID - Handler deserializes payload via
AFPluginData<CreateViewPayloadPB>::from_request() - Handler processes and returns
AFPluginData<ViewPB>(orFlowyError) - Response serialized to bytes, returned through FFI
- Dart deserializes the Protobuf response
Byte Traits
The byte_trait.rs file defines the serialization contract:
ToBytes— serialize a type toBytes(implemented for all Protobuf types via blanket impl)AFPluginFromBytes— deserialize fromBytes(implemented for all Protobuf types)
These traits use TryInto<Bytes> and TryFrom<Bytes> with ProtobufError as the error type, ensuring that only types with valid Protobuf schemas can cross the FFI boundary.
8.2 Rust Crate Reference
flowy-core (Orchestrator)
Location: frontend/rust-lib/flowy-core/src/ (10 files, ~75K chars)
The central crate that wires everything together:
| File | Purpose |
|---|---|
lib.rs |
AppFlowyCore struct — holds Arc references to all managers |
app_life_cycle.rs |
AppLifeCycleImpl — authentication lifecycle handler |
server_layer.rs |
ServerProvider — manages cloud server type and connection |
full_indexed_data_provider.rs |
Background full-content indexing for search |
instant_indexed_data_provider.rs (implied) |
Real-time content indexing |
AppFlowyCore::new() is the application's bootstrap function:
- Creates the
AFPluginRuntime(Tokio runtime) - Instantiates
UserManagerwith user paths and session config - Creates
ServerProviderwith cloud configuration - Instantiates all domain managers (Folder, Database, Document, Search, AI, Storage)
- Creates
AppLifeCycleImpland registers it withUserManager - Registers all
AFPlugininstances with the dispatch runtime - Returns the configured
AppFlowyCoreready for events
flowy-user (Authentication & User Management)
Location: frontend/rust-lib/flowy-user/src/ (4 files, ~43K chars)
| Component | Purpose |
|---|---|
UserManager |
Central user state: session, profile, workspace list |
| Event handlers | sign_in_with_email_password, sign_up, sign_out, delete_account, get_user_profile, update_user_profile |
UserCloudConfig |
Cloud sync toggle, encryption toggle, encryption secret |
| Data import/export | Cross-workspace and cross-device data portability |
flowy-user-pub (flowy-user-pub/src/) — Public types shared with other crates:
UserProfile— user metadata (name, email, avatar, workspace list)UserWorkspace— workspace identity and typeWorkspaceType—Localvs cloud workspace enum- Cloud service provider traits
Migrations (flowy-user/src/migrations/, 9 files, ~16K chars):
Sequential database schema migrations. Each migration is a numbered module (e.g., m001_initial.rs through m009_*). Migrations are discovered and executed in order by the migration runner during authentication.
Services (flowy-user/src/services/):
UserConfig— device configuration and data pathsUserPaths— filesystem path resolution for user data directories- Entity types for internal service communication
flowy-folder (Workspace & View Hierarchy)
Location: frontend/rust-lib/flowy-folder/src/ (11 files, ~146K chars — the largest module)
This is the most complex crate, managing the entire workspace tree:
| Component | Purpose |
|---|---|
FolderManager |
Central manager: workspace, views, favorites, recent, trash, sections |
| View CRUD | create_view, get_view, update_view, delete_view, move_view, duplicate_view |
| View types | Document, Grid, Board, Calendar |
| Spaces | Public and private view collections |
| Share/Publish | View sharing and public publishing |
| Import | CSV, AFDoc format import |
| Workspace members | Role-based access: Owner, Member, Guest |
| Sections | Favorites, Recent, Trash — special view collections |
flowy-folder-pub (flowy-folder-pub/src/):
- Public types for folder entities
ViewPB,WorkspacePB,TrashPBProtobuf types- Folder cloud service traits
The folder data is a hierarchical tree stored as a CRDT collab object. Each view has:
view_id(UUID)parent_view_id(UUID, for hierarchy)name(string)layout(Document, Grid, Board, Calendar)created_at/updated_at(timestamps)icon(optional emoji or custom)extra(JSON blob for layout-specific metadata)
flowy-document (Block-Based Documents)
Location: frontend/rust-lib/flowy-document/src/ (11 files, ~62K chars)
| Component | Purpose |
|---|---|
DocumentManager |
Open/close/create documents, editor cache |
| Block data model | blocks map, children_map, text_map |
| Awareness states | Real-time collaboration cursor/selection sharing |
| Snapshots | Point-in-time document state capture |
| Sync state | Subscription-based sync status monitoring |
| Notifications | DocumentNotification enum for UI updates |
flowy-document-pub (flowy-document-pub/src/):
- Public document types shared across crates
- Cloud service traits for document sync
Documents use a block-based data model where each block has:
- A unique ID
- A type (paragraph, heading, list, code, image, table, etc.)
- A
delta(rich text operations in Quill-like format) - Children (nested blocks via
children_map) - Attributes (formatting, alignment, etc.)
The document collab object is a Yjs Doc with three root-level maps:
blocks— block ID to block datachildren_map— block ID to ordered child IDstext_map— block ID to rich textYText
flowy-database2 (Grid, Board, Calendar)
Location: frontend/rust-lib/flowy-database2/src/ (6 files, ~114K chars)
| Component | Purpose |
|---|---|
DatabaseManager |
Database lifecycle, editor cache management |
| Database views | Grid, Board, Calendar layout rendering |
| Fields (columns) | Type system with rich type options |
| Rows | Row CRUD, cell access, grouping |
| Filters | Per-field filter conditions |
| Sorts | Multi-field sorting |
| Groups | Group-by for Board/Calendar views |
| Calculations | Aggregation functions on fields |
| CSV export | CSVFormat for data export |
Field Type System:
| Field Type | Purpose |
|---|---|
RichText |
Plain/rich text content |
Number |
Numeric values with format options (currency, percentage, etc.) |
DateTime |
Date and optional time with timezone |
SingleSelect |
One option from a predefined set |
MultiSelect |
Multiple options from a predefined set |
Checkbox |
Boolean toggle |
URL |
Hyperlink with title |
Checklist |
Sub-item checklist with completion tracking |
Media |
File/image attachments |
Relation |
Cross-database row reference |
Timestamp |
Auto-generated created/modified timestamps |
Services (flowy-database2/src/services/):
field/type_options/— individual type option implementationsgroup/— grouping logic for Board and Calendar viewsfilter_test/— filter evaluation engine
flowy-database-pub (flowy-database-pub/src/):
- Public database types and cloud service traits
Utils (flowy-database2/src/utils/):
- CSV generation utilities
- Database math/aggregation helpers
flowy-ai (AI Integration)
Location: frontend/rust-lib/flowy-ai/src/ (11 files, ~119K chars)
| Component | Purpose |
|---|---|
AIManager |
Chat lifecycle, model selection, capability management |
Chat |
Individual chat session state and message history |
AICompletion |
Text completion and suggestion tasks |
LocalAIController |
Ollama integration for local model inference |
ChatServiceMiddleware |
Routes requests between cloud AI and local AI |
ModelSelectionControl |
Manages available models from server and local sources |
AI Entity Types:
| Protobuf Type | Purpose |
|---|---|
ChatInfoPB |
Chat session metadata |
SendChatPayloadPB |
Send a message to a chat |
StreamChatPayloadPB |
Stream a chat response |
AIModelPB |
Model identity and capabilities |
Middleware (flowy-ai/src/middleware/):
The ChatServiceMiddleware implements the routing logic:
- Check if local AI is enabled and a local model is available
- If local: route to
LocalAIControllerwhich communicates with Ollama - If cloud: route to the cloud AI service via
flowy-server - Handle streaming responses from either source
flowy-ai-pub (flowy-ai-pub/src/):
- Public AI types and service traits
AIModelPB,CompletionTypePBshared across crates
Note
Local AI via Ollama is fully optional. When enabled, AppFlowy manages the Ollama process lifecycle, downloads requested models, and routes AI requests locally. This provides offline AI capability without cloud dependency. The
LocalAIControllermonitors Ollama process health and falls back to cloud AI if the local process fails.
flowy-search (Full-Text Search)
Location: frontend/rust-lib/flowy-search/src/ (3 files, ~2K chars + services ~7K chars)
| Component | Purpose |
|---|---|
SearchManager |
Tantivy index lifecycle, search execution |
| Stream-based search | Results delivered via Dart port for async UI updates |
| Index management | Per-workspace Tantivy index creation and maintenance |
Services (flowy-search/src/services/):
- Index creation and schema definition
- Document indexing (title, content, metadata)
- Query parsing and execution
- Result ranking and formatting
flowy-search-pub (flowy-search-pub/src/):
tantivy_state_initmodule — Tantivy state creation and teardown (close_document_tantivy_state())- Public search types shared across crates
The Tantivy schema indexes:
- Document title (tokenized, stored)
- Document content (tokenized, not stored in full — snippets generated from highlights)
- View ID (stored, for linking results to views)
- Workspace ID (for index isolation)
flowy-storage (File Management)
Location: frontend/rust-lib/flowy-storage/src/ (9 files, ~52K chars)
| Component | Purpose |
|---|---|
StorageManager |
File upload queue, progress tracking, cache management |
| Chunked upload | Large files split into chunks for reliable upload |
| File cache | Temporary storage before upload, cached downloads |
| Events | RegisterStream (upload progress port), QueryFile (file metadata) |
flowy-storage-pub (flowy-storage-pub/src/):
- Public storage types and cloud service traits
- File metadata, upload status enums
The upload pipeline:
- File placed in local cache directory
StorageManagersplits into chunks (configurable chunk size)- Each chunk uploaded independently with retry logic
- Progress reported via registered stream port
- On completion, file URL returned and cache entry marked complete
- Failed uploads are retried with exponential backoff
flowy-server (Cloud Abstraction)
Location: frontend/rust-lib/flowy-server/src/ (multi-file)
| Component | Purpose |
|---|---|
ServerProvider |
Manages cloud server type and connection lifecycle |
af_cloud/ |
AppFlowy Cloud implementation via client_api |
LoggedUser trait |
Abstraction for the currently authenticated user |
The flowy-server crate abstracts all cloud interactions behind traits defined in flowy-server-pub:
UserCloudServiceProvider— authentication and user management- Folder, Document, Database cloud service traits
- Storage cloud service (file upload/download)
- AI cloud service (chat, completion)
flowy-server-pub (flowy-server-pub/src/):
- All cloud service trait definitions
- Server configuration types
- Shared error types for cloud operations
Cloud communication uses:
- REST API via
client_apifor CRUD operations - WebSocket for real-time collab sync
- Streaming HTTP for AI chat responses
collab-integrate (CRDT Collaboration)
Location: frontend/rust-lib/collab-integrate/src/ (5 files, ~28K chars)
| Component | Purpose |
|---|---|
AppFlowyCollabBuilder |
Factory for creating collab objects |
| Plugin configuration | Local storage and cloud sync plugins per collab |
| Persistence | CollabDB (RocksDB-based) via KV store |
The AppFlowyCollabBuilder creates collab objects (Collab from the collab crate family) with the appropriate plugins:
AppFlowyCollabBuilder
├── Local storage plugin (CollabDB persistence)
├── Cloud sync plugin (WebSocket updates)
└── Type-specific plugin (document, database, folder)
Each collab object wraps a Yjs Doc:
collab_document— block-based document CRDTcollab_database— database/grid/board CRDTcollab_folder— workspace hierarchy CRDT
The builder configures:
CollabPersistenceConfig— local persistence settingsCollabCloudPluginProvider— cloud sync channelCollabDataSource— initial data source (local disk or cloud fetch)
Important
Collab objects are created with
Weakreferences to avoid circular ownership. The builder usesWeak<AppFlowyCollabBuilder>itself. If the builder is dropped while collab objects are still alive, new collab creation will fail withFlowyError::ref_drop(). This is by design — collab objects should not outlive the application core.
flowy-notification (Notification System)
Location: frontend/rust-lib/flowy-notification/src/
The notification crate provides typed notifications from Rust to Dart:
- Each domain defines its own notification enum (e.g.,
FolderNotification,DocumentNotification,DatabaseNotification) - Notifications are sent via a registered Dart port
- The Flutter layer subscribes to notification streams per-entity
flowy-error (Error System)
Location: frontend/rust-lib/flowy-error/src/
Defines the application-wide error type:
| Component | Purpose |
|---|---|
ErrorCode enum |
Canonical error variants |
FlowyError |
Error struct with code + message + context |
FlowyResult<T> |
Result<T, FlowyError> alias |
Key error codes beyond security (covered in Section 7.6):
| Error Code | Meaning |
|---|---|
AlreadyExists |
Duplicate resource creation attempt |
AIResponseLimitExceeded |
AI usage quota exhausted |
AIImageResponseLimitExceeded |
AI image generation quota exhausted |
AIMaxRequired |
AI feature requires higher subscription tier |
LocalAINotReady |
Local AI (Ollama) not initialized |
LocalAIDisabled |
Local AI feature disabled in settings |
flowy-date (Date Utilities)
Location: frontend/rust-lib/flowy-date/src/
Date parsing and formatting utilities used by the database date field type. Handles timezone conversion, date format strings, and relative date calculations.
lib-infra (Infrastructure Utilities)
Location: frontend/rust-lib/lib-infra/src/
Shared infrastructure utilities:
async_traitre-export for async trait definitions- Utility types used across crates
- Platform abstraction helpers
lib-log (Logging Infrastructure)
Location: frontend/rust-lib/lib-log/src/
Logging setup and configuration:
- Initializes the
tracingsubscriber with formatted output - Configures log level from
RUST_LOGenvironment variable - Provides log file rotation (if configured)
build-tool (Code Generation)
Location: frontend/rust-lib/build-tool/
Three crates for compile-time code generation:
| Crate | Purpose |
|---|---|
flowy-ast |
Parses Rust struct definitions annotated with #[derive(ProtoBuf)] to extract field metadata (field numbers, types, optionality) |
flowy-codegen |
Generates .proto files from parsed AST, generates Dart event dispatch classes, generates Rust event handler boilerplate |
flowy-derive |
Procedural macros: ProtoBuf (struct serialization), ProtoBuf_Enum (enum serialization), event handler macros |
The code generation pipeline:
- Developer annotates a Rust struct with
#[derive(ProtoBuf)] flowy-deriveproc macro invokesflowy-astto parse the structflowy-codegengenerates the corresponding.protofileprotoccompiles to Dart and Rust bindings- Event map generation creates the Dart-side event dispatch classes
8.3 Flutter Layer Architecture
Entry Point
lib/main.dart calls runAppFlowy() from the startup package:
Future<void> main() async {
ScaledWidgetsFlutterBinding.ensureInitialized(
scaleFactor: (_) => 1.0,
);
await runAppFlowy();
}
runAppFlowy() (in lib/startup/startup.dart) initializes:
- Platform-specific bindings
- Rust FFI bridge
- Environment configuration
- Root widget tree
Plugin System
Location: lib/startup/plugin/ and lib/plugins/
The Flutter plugin system registers view types that users can create:
| Plugin | View Type | Purpose |
|---|---|---|
| Document | Document | Rich text editor with block-based content |
| Database (Grid) | Grid | Spreadsheet-like table view |
| Database (Board) | Board | Kanban board view |
| Database (Calendar) | Calendar | Calendar event view |
| Trash | Trash | Deleted items recovery |
Each plugin provides:
- A
PluginBuilderfor creating new instances - Widget builders for the view content
- Menu item builders for the sidebar
- Event handlers for plugin-specific actions
Plugin registration happens in lib/startup/plugin/src/ where each plugin type is registered with the plugin registry.
State Management
AppFlowy uses the BLoC (Business Logic Component) pattern throughout the Flutter layer:
- Each feature has a corresponding BLoC in
application/directories - BLoCs communicate with Rust via event dispatch
- UI widgets observe BLoC state via
BlocBuilder/BlocListener - Settings BLoCs:
CloudSettingBloc,AppearanceSettingsCubit,NotificationSettingsBloc
Flutter Package Structure
The packages/ directory contains reusable Flutter packages:
| Package | Purpose |
|---|---|
appflowy_backend |
FFI bridge to Rust (FlowySDK class) |
appflowy_popover |
Custom popover/dropdown widget |
appflowy_result |
Result type utilities (FlowyResult Dart equivalent) |
appflowy_ui |
Shared UI components (buttons, modals, text fields, avatars, menus) |
flowy_infra |
Infrastructure: color schemes, platform utils |
flowy_infra_ui |
Platform-specific UI implementations (Android, Web, platform interface) |
flowy_svg |
SVG rendering utilities |
Authentication Layer
Location: lib/user/application/auth/
Three AuthService implementations:
CloudAuthService— production cloud authenticationBackendAuthService— direct backend auth (development)MockAuthService— in-memory auth for tests
The auth layer wraps Rust-side auth events, providing a Dart-friendly interface for sign-in flows, token management, and session persistence.
Settings Architecture
Location: lib/workspace/application/settings/ and lib/features/settings/
Settings are split across multiple BLoCs:
| BLoC | Controls |
|---|---|
CloudSettingBloc |
Cloud sync toggle, server configuration |
AppearanceSettingsCubit |
Theme, font, locale, layout density |
NotificationSettingsBloc |
Push notification preferences |
Settings data models live in lib/features/settings/data/models/ with corresponding presentation widgets in lib/workspace/presentation/settings/.
8.4 Data Model Deep Dive
Collab Object Hierarchy
Workspace (collab_folder)
├── Space (public or private)
│ ├── View (Document)
│ │ └── collab_document
│ ├── View (Grid)
│ │ └── collab_database
│ ├── View (Board)
│ │ └── collab_database (same type, different layout)
│ └── View (Calendar)
│ └── collab_database (same type, different layout)
├── Favorites (section)
├── Recent (section)
└── Trash (section)
Each collab object is identified by:
object_id(UUID) — unique identifiercollab_type(enum) — Document, Database, Folder, etc.workspace_id(UUID) — owning workspace
Document Data Model
{
"blocks": {
"block_id_1": {
"id": "block_id_1",
"type": "paragraph",
"data": { "delta": [...] },
"parent": "page_block_id",
"children": "children_id_1"
}
},
"children_map": {
"children_id_1": ["block_id_2", "block_id_3"]
},
"text_map": {
"block_id_1": "<YText CRDT object>"
}
}
Block types include: paragraph, heading (h1-h6), bulleted_list, numbered_list, checkbox, quote, code, divider, image, table, callout, math_equation, toggle_list, outline, and more.
Rich text uses a Quill-compatible delta format:
[
{ "insert": "Hello " },
{ "insert": "world", "attributes": { "bold": true } },
{ "insert": "!" }
]
Database Data Model
A database collab object contains:
- Fields — column definitions with type options
- Rows — data rows, each containing cells keyed by field ID
- Views — named configurations (Grid, Board, Calendar) with filters, sorts, groups
- Meta — database-level settings
Each field has a TypeOptionData blob specific to its type:
RichTextTypeOption— no extra configNumberTypeOption— format (decimal, currency, percentage), scale, symbolDateTypeOption— date format, time format, timezoneSelectTypeOption— option list with colorsChecklistTypeOption— sub-option tracking
8.5 Notification System
Notifications flow from Rust to Dart through a port-based mechanism:
Rust handler
│
▼
NotificationSender::send(subject_id, notification_type, payload)
│
▼
Dart port (registered per-entity)
│
▼
NotificationListener<T> (Dart widget/BLoC)
│
▼
UI update
Each domain defines notification types:
| Domain | Example Notifications |
|---|---|
| Folder | DidUpdateWorkspace, DidUpdateView, DidUpdateTrash, DidUpdateFavorites |
| Document | DidUpdateContent, DidUpdateAwareness |
| Database | DidUpdateFields, DidUpdateRows, DidUpdateCells, DidUpdateFilters |
| User | DidUpdateUserProfile, DidUpdateWorkspace |
8.6 Testing Architecture
Integration Tests
Location: frontend/appflowy_flutter/integration_test/
| Directory | Platform | Scope |
|---|---|---|
desktop/settings/ |
Desktop | Settings page flows |
desktop/cloud/ |
Desktop | Cloud-connected scenarios |
mobile/settings/ |
Mobile | Mobile settings flows |
Integration tests use Flutter's IntegrationTestWidgetsFlutterBinding and run the full application stack.
Rust Event Integration Tests
Location: frontend/rust-lib/event-integration-test/src/
These tests exercise the Rust layer directly through the event dispatch system:
document/— document creation, editing, collab operations- Database CRUD, field operations, view switching
- User authentication flows
- Folder operations
Unit Tests
- Dart unit tests:
frontend/appflowy_flutter/test/unit_test/— pure unit testsbloc_test/— BLoC state transition testswidget_test/— widget rendering tests
- Rust unit tests: in-crate
#[cfg(test)]modulesflowy-document/tests/— document operation testsflowy-database2/tests/— database operation tests (filter, layout, mock data)
Test Configuration
- BLoC tests:
test/bloc_test/lib/features/settings/— settings-specific BLoC tests - Widget tests:
test/widget_test/lib/— widget rendering validation
Tip
When adding a new event handler, write a Rust event integration test first (
event-integration-test/). This catches serialization errors and handler registration issues before the Flutter layer is involved. The integration test framework creates a fullAppFlowyCorein-process, so tests exercise the real dispatch path.
9. Integration Points
9.1 AppFlowy Cloud
AppFlowy Cloud is the official cloud backend providing:
| Feature | Protocol | Endpoint |
|---|---|---|
| Authentication | REST (GoTrue) | gotrue_url |
| User management | REST | base_url/api/ |
| Collab sync | WebSocket | ws_url |
| File storage | REST | base_url/api/file_storage/ |
| AI services | REST (streaming) | base_url/api/ai/ |
The client_api crate (external dependency) handles all cloud communication. ServerProvider in flowy-server manages the connection lifecycle:
- On authentication: establish REST session, connect WebSocket
- On collab open: subscribe to real-time updates for that object
- On local edit: push update through WebSocket
- On remote update: apply to local collab object (CRDT merge)
- On disconnect: queue updates locally, replay on reconnect
9.2 Ollama (Local AI)
The LocalAIController in flowy-ai manages local AI through Ollama:
| Operation | Implementation |
|---|---|
| Process management | Start/stop Ollama process |
| Model management | Download, list, select models |
| Chat inference | Send prompts, stream responses |
| Text completion | Inline text suggestions |
| Health monitoring | Process health checks, auto-restart |
Configuration is per-workspace: users can select different models for different workspaces. The ModelSelectionControl aggregates available models from both server (cloud) and local (Ollama) sources.
Note
Local AI model selection persists in the user's workspace settings. Switching between local and cloud AI does not affect conversation history — the
ChatServiceMiddlewareabstracts the backend transparently.
9.3 Subscription and Billing
The SubscriptionPlan enum from client_api::entity::billing_dto defines plan tiers:
| Plan | AI Capabilities |
|---|---|
| Free | Basic AI with response limits |
| Pro | Extended AI limits |
| AI Max | Unlimited AI responses, image generation |
The AIMaxRequired error code gates features requiring higher tiers. AIResponseLimitExceeded and AIImageResponseLimitExceeded enforce per-period quotas.
9.4 Import/Export
| Format | Direction | Handler |
|---|---|---|
| CSV | Import | flowy-folder import handler |
| CSV | Export | flowy-database2 CSVFormat export |
| AFDoc | Import | Native AppFlowy document format import |
| Markdown | Import/Export | Document content conversion |
9.5 Internationalization
Location: project.inlang/
AppFlowy uses the inlang framework for internationalization:
- Translation files define all UI strings
- Flutter
AppLocalizationsgenerated from translation sources - Locale selection persisted in
SharedPreferences
9.6 Theme System
AppFlowy supports custom themes through the flowy_infra package:
colorscheme/— named color scheme definitionsAppearanceSettingsCubit— manages active theme- Custom theme import/export (issue #8663 notes themes can be lost after update)
- Light and dark mode variants
9.7 Platform-Specific Integration
| Platform | Integration |
|---|---|
| macOS | Native window management, menu bar integration |
| Windows | Windows Runner (flutter_window.h), COM initialization |
| Linux | GTK integration, X11/Wayland support |
| Android | CMake native build, JNI library loading, libc++_shared.so bundling (arm64-v8a, armeabi-v7a, x86_64) |
| iOS | Xcode project, Codemagic CI |
| Web | Flutter Web, custom index.html, potential Tauri wrapping |
9.8 Known Issues and Operational Considerations
Based on community-reported issues at the documented commit:
| Issue | Impact | Workaround |
|---|---|---|
#8655 libvdpau.so.1 missing |
Linux: app fails to start | Install libvdpau1 package |
| #8577 Windows 10 crash | Fresh install crash on Windows 10 | Update to latest version or Windows 11 |
| #8668 Settings page error offline | Settings page throws without internet | Ensure offline fallback in cloud settings BLoC |
| #8635 Cloud restart offline = data loss | Data loss when cloud service restarts while user is offline | Avoid toggling cloud settings without connectivity |
| #8582 Stale sidebar views | Sidebar shows outdated view list | Restart application to refresh |
| #8651 Nested grid undo crash | Undo in nested grids causes grid disappearance | Avoid deep undo in nested database views |
Caution
Issue #8635 describes a data loss scenario: if the cloud service restarts while the user is offline, and the user has unsynced local changes, those changes may be lost when sync resumes. This is a fundamental challenge of the local-first sync model. Until this is resolved, advise users to ensure they have a working cloud connection before making critical edits to shared workspaces.
9.9 Feature Roadmap Context
Notable open feature requests that affect architectural planning:
| Request | Architectural Impact |
|---|---|
| #718 Plugin marketplace | Requires plugin sandboxing, API versioning, distribution system |
| #6403 Windows ARM build | New build profile, CI pipeline, potential Rust target issues |
| #8438 E2E Encrypted Sync | Deep changes to collab sync protocol, key management |
| #6683 Cross-workspace search | Tantivy index architecture change (currently per-workspace) |
Appendix A: Crate Dependency Graph
flowy-core (orchestrator)
├── flowy-user
│ ├── flowy-user-pub
│ ├── flowy-sqlite
│ └── flowy-error
├── flowy-folder
│ ├── flowy-folder-pub
│ ├── flowy-error
│ └── flowy-notification
├── flowy-document
│ ├── flowy-document-pub
│ ├── flowy-error
│ └── flowy-notification
├── flowy-database2
│ ├── flowy-database-pub
│ ├── flowy-error
│ ├── flowy-notification
│ └── flowy-date
├── flowy-ai
│ ├── flowy-ai-pub
│ ├── flowy-error
│ └── flowy-notification
├── flowy-search
│ ├── flowy-search-pub
│ └── flowy-error
├── flowy-storage
│ ├── flowy-storage-pub
│ └── flowy-error
├── flowy-server
│ ├── flowy-server-pub
│ ├── client_api (external)
│ └── flowy-error
├── collab-integrate
│ ├── collab (external crate family)
│ └── collab_plugins
├── lib-dispatch
│ ├── protobuf
│ ├── bytes
│ └── validator
├── lib-infra
├── lib-log
│ └── tracing
├── dart-ffi
│ └── lib-dispatch
└── build-tool
├── flowy-ast
├── flowy-codegen
└── flowy-derive
Appendix B: Event Map Reference
Each crate registers events through its event_map.rs. The event enum name follows the pattern {Domain}Event:
| Crate | Event Enum | Example Events |
|---|---|---|
flowy-user |
UserEvent |
SignInWithEmailPassword, SignUp, SignOut, DeleteAccount, GetUserProfile, UpdateUserProfile |
flowy-folder |
FolderEvent |
CreateView, ReadView, UpdateView, DeleteView, MoveView, DuplicateView, CreateSpace, GetFavorites, GetRecentViews, GetTrash |
flowy-document |
DocumentEvent |
CreateDocument, OpenDocument, CloseDocument, ApplyAction, GetDocumentSnapshot |
flowy-database2 |
DatabaseEvent |
CreateDatabase, OpenDatabase, GetFields, CreateField, UpdateField, CreateRow, GetRow, UpdateCell, GetFilters, UpdateSort |
flowy-ai |
AIEvent |
CreateChat, SendMessage, StreamMessage, GetModels, SelectModel, CompleteText |
flowy-search |
SearchEvent |
Search, IndexDocument |
flowy-storage |
StorageEvent |
RegisterStream, QueryFile |
Tip
To trace the full path of any user action: find the Dart event dispatch call (usually in a BLoC or service class), note the event name, find the corresponding Rust event enum variant, locate the handler function in
event_map.rs, then follow the handler implementation. This is the single most effective debugging technique for AppFlowy.
Appendix C: Protobuf Schema Conventions
All Protobuf types follow these conventions:
| Convention | Example |
|---|---|
| Payload suffix for input types | CreateViewPayloadPB |
| PB suffix for all Protobuf types | ViewPB, UserProfilePB |
| Field numbering starts at 1 | Standard Protobuf practice |
Optional fields use oneof or wrapper types |
Option<T> in Rust maps to optional Protobuf field |
| Enum values start with the enum name | ViewLayout.Document, ViewLayout.Grid |
The #[derive(ProtoBuf)] macro on Rust structs generates:
- A
.protoschema file - Rust
TryFrom<Bytes>andTryInto<Bytes>implementations - Dart class with matching fields and serialization
The #[pb(index = N)] attribute on struct fields sets the Protobuf field number, which must remain stable across versions for backward compatibility.