# How AppFlowy Manages Workspace and View Hierarchy in Its UI: A Deep Dive into the Folder Architecture

> Discover how AppFlowy manages its workspace and view hierarchy with its folder architecture. Learn about the tree-based structure and CRDT synchronization.

- Repository: [AppFlowy-IO/AppFlowy](https://github.com/AppFlowy-IO/AppFlowy)
- Tags: deep-dive
- Published: 2026-03-03

---

**AppFlowy implements a tree-based workspace and view hierarchy using the `FolderManager` service and `collab_folder` crate**, where each view stores its parent's ID to form a traversable tree structure synchronized via CRDTs across devices.

AppFlowy organizes content through a folder-based collaborative model that treats **workspaces** as top-level containers and **views** as nested documents, boards, or databases. The **workspace and view hierarchy** is managed by the `FolderManager` struct in the `flowy-folder` crate, which bridges UI interactions with the underlying `Folder` object from the `collab_folder` library. This architecture enables real-time collaboration by ensuring all hierarchy mutations update a shared CRDT-based state that syncs across local persistence and cloud services.

## The FolderManager: Central Hub for Hierarchy Operations

The `FolderManager` in [`frontend/rust-lib/flowy-folder/src/manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-folder/src/manager.rs) serves as the core service coordinating all **workspace and view hierarchy** changes. It maintains a thread-safe reference to the shared `Folder` state using `ArcSwapOption<RwLock<Folder>>`, allowing concurrent read access while ensuring exclusive write access during mutations.

All hierarchy modifications flow through this manager, including view creation, parent reassignment, trash operations, and tree traversal queries. The `FolderManager` translates high-level UI requests into low-level `Folder` operations, persists changes via the collab backend, and emits notifications to update the Flutter frontend in real time.

## Workspace Representation and Retrieval

Workspaces in AppFlowy are represented by two primary structures that bridge the internal model and the UI protocol.

**`WorkspacePB`** (defined in [`frontend/rust-lib/flowy-folder/src/entities/workspace.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-folder/src/entities/workspace.rs)) is the Protobuf DTO sent to clients, containing the workspace ID, name, list of top-level views, and creation timestamp. The internal `Workspace` model (from the external `collab_folder` crate) stores the canonical state, with one workspace per user owned by the `Folder` object.

To retrieve workspace data, the `FolderManager` provides two key methods:

- **`get_current_workspace()`**: Returns the currently active `WorkspacePB` by reading the `Folder` state and converting the internal `Workspace` model (lines 131-151 in [`manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager.rs)).
- **`get_workspace_pb()`**: Returns a complete `WorkspacePB` containing **all** views including private ones, useful for workspace-level exports or administrative operations (lines 536-559 in [`manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager.rs)).

## View Hierarchy: Tree Structure and Parent References

The **view hierarchy** forms a tree where every view is a node containing a `parent_view_id` field pointing to its immediate ancestor. This parent-reference pattern enables efficient traversal both upward (to ancestors) and downward (to descendants) without maintaining complex adjacency lists.

**`ViewPB`** (defined in [`frontend/rust-lib/flowy-folder/src/entities/view.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-folder/src/entities/view.rs)) represents views in the UI layer, containing:
- `id`: Unique identifier (UUID)
- `parent_view_id`: The parent node's ID (root views use the workspace ID)
- `name`, `layout` (Document, Board, Grid, Calendar), `icon`
- Child view lists, favorite flags, and lock status

The crate provides three serialization helpers for different traversal depths:

- **`view_pb_without_child_views()`**: Serializes a view as a leaf node without children (lines 91-107).
- **`view_pb_with_child_views()`**: Embeds **only the first level** of children, optimizing for standard UI display (lines 27-48).
- **`view_pb_with_all_child_views()`**: Recursively nests **all descendants**, used for deep exports or publishing (lines 49-84).

## Core Hierarchy CRUD Operations

All tree mutations operate on the shared `Folder` instance managed by `FolderManager`, ensuring atomic updates to the CRDT state.

### Creating Views with Parent Relationships

**`create_view_with_params()`** (lines 661-716 in [`manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager.rs)) handles view creation by:
1. Invoking a layout-specific `FolderOperationHandler` to create the underlying collab object (document, database, etc.).
2. Inserting the view into the `Folder` tree using the supplied `parent_view_id` to determine position.
3. Optionally setting the view as the current active view and assigning it to a section (Public or Private).

**`insert_views_with_parent()`** (lines 562-580) handles bulk insertions, rewriting the `parent_view_id` of each supplied view or defaulting to the most recently edited view if no parent is specified.

### Moving Views Within and Between Parents

AppFlowy supports two distinct move semantics:

- **`move_view()`** (lines 442-470): Reorders a view within its **current parent** by translating UI indices to actual indices in the parent's child list, then calling `Folder::move_view` to update the CRDT.
- **`move_nested_view()`** (lines 904-934): Moves a view to a **different parent**, updating the `parent_view_id` field, reordering the target sibling list based on `prev_view_id`, and optionally changing the view's visibility section (private to public or vice versa).

### Traversing Ancestors and Descendants

**`get_view_pb()`** (lines 671-724) resolves a view by ID, filters out trashed or private views based on permissions, and returns a `ViewPB` with first-level children using `view_pb_with_child_views()`.

**`get_view_ancestors_pb()`** (lines 696-712) walks upward from a given view via `parent_view_id` until reaching the workspace root, returning the breadcrumb path from root to leaf.

**`move_view_to_trash()`** (lines 818-859) performs cascading deletion by marking a view and **all its descendants** as trashed, removing them from favorites, and notifying observers to update the UI.

## Data Transfer Objects: WorkspacePB and ViewPB

The separation between internal models and presentation models ensures backward compatibility and network efficiency.

- **`WorkspacePB`** (lines 15-28 in [`entities/workspace.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/entities/workspace.rs)): Protobuf definition for workspace serialization, containing metadata and top-level view IDs.
- **`ViewPB`** (lines 39-88 in [`entities/view.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/entities/view.rs)): Comprehensive Protobuf struct capturing view metadata, layout type, and nested children.

Conversion between internal `collab_folder` types and these PBs happens in the manager layer, allowing the core logic to evolve independently of the UI protocol.

## Real-Time Synchronization and Notifications

Whenever the **workspace and view hierarchy** changes, `FolderManager` emits folder notifications through the observer system defined in [`frontend/rust-lib/flowy-folder/src/manager_observer.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/rust-lib/flowy-folder/src/manager_observer.rs).

Key notification patterns include:
- **`DidMoveViewToTrash`**: Broadcast when views are soft-deleted, triggering UI removal.
- **`DidUpdateWorkspaceSetting`**: Fired when workspace metadata changes.
- **`notify_child_views_changed`** and **`notify_parent_view_did_change`**: Helper functions that ensure both the moved view and its parent containers refresh in the UI.

The underlying `Folder` object is a **collab object** (`CollabType::Folder`) initialized in `FolderManager::make_folder` or `initialize_after_sign_in`. All mutations update the CRDT document, enabling real-time collaboration across devices while maintaining local persistence.

## Practical Code Examples

### Example 1: Creating a Document View Under a Parent

```rust
use flowy_folder::entities::{CreateViewParams, ViewLayoutPB};
use uuid::Uuid;

// Assume we already have a FolderManager instance `manager`
let parent_id = Uuid::parse_str("5f8e4a1d‑c5b2‑4a1e‑a0f7‑123456789abc")?;
let params = CreateViewParams {
    parent_view_id: parent_id,
    name: "My new doc".to_string(),
    layout: ViewLayoutPB::Document,
    view_id: gen_view_id(),
    initial_data: ViewData::Data(vec![]),
    meta: Default::default(),
    set_as_current: true,
    index: None,
    section: Some(ViewSectionPB::Public),
    extra: None,
    icon: None,
};
let (view, _) = manager.create_view_with_params(params, true).await?;
println!("Created view {}", view.id);

```

This creates a view whose `parent_view_id` points to the supplied parent, automatically inserting it into the folder tree.

### Example 2: Moving a View to a Different Parent

```rust
use flowy_folder::entities::MoveNestedViewParams;

// Move view `v1` under new parent `v2`, placing it after sibling `v3`
let params = MoveNestedViewParams {
    view_id: Uuid::parse_str("v1‑uuid")?,
    new_parent_id: Uuid::parse_str("v2‑uuid")?,
    prev_view_id: Some(Uuid::parse_str("v3‑uuid")?),
    from_section: None,
    to_section: Some(ViewSectionPB::Public),
};
manager.move_nested_view(params).await?;

```

The function updates `parent_view_id` of `v1`, reorders the sibling list, and sends the appropriate notifications to connected clients.

### Example 3: Retrieving the Full Descendant Tree

```rust
let view_id = "5f8e4a1d‑c5b2‑4a1e‑a0f7‑123456789abc";
let root_pb = manager.get_view_pb(view_id).await?; // first-level children
let full_tree = view_pb_with_all_child_views(
    Arc::clone(&folder.get_view(view_id).unwrap()),
    &|id| manager.get_views_belong_to(id).await.unwrap(),
);

```

`view_pb_with_all_child_views` recursively collects all nested children, useful for exporting a complete page hierarchy or generating static sites.

## Summary

- **Tree Structure**: AppFlowy uses a parent-reference model where each view stores its `parent_view_id`, forming a traversable hierarchy within a workspace.
- **Central Service**: The `FolderManager` in [`manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager.rs) coordinates all CRUD operations, protecting shared state with `ArcSwapOption<RwLock<Folder>>`.
- **DTO Layer**: `WorkspacePB` and `ViewPB` in the `entities` module provide protocol-buffer interfaces between the Rust backend and Flutter UI.
- **Traversal Methods**: Specialized helpers like `view_pb_with_all_child_views` and `get_view_ancestors_pb` support both shallow UI rendering and deep tree exports.
- **Real-Time Sync**: All hierarchy changes update a CRDT-based `Folder` object, ensuring immediate synchronization across devices via the collab infrastructure.

## Frequently Asked Questions

### How does AppFlowy handle moving a view between different parents?

AppFlowy uses the **`move_nested_view()`** method in `FolderManager` (lines 904-934 of [`manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager.rs)). This function updates the view's `parent_view_id` to the new parent's ID, recalculates the position within the target sibling list using `prev_view_id`, and optionally transfers the view between sections (Public or Private). The operation is atomic within the CRDT and triggers notifications to all connected clients.

### What is the difference between `ViewPB` and the internal `View` model?

**`ViewPB`** is a Protobuf DTO defined in [`entities/view.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/entities/view.rs) that serializes view data for transmission to the Flutter frontend, including all metadata and optional child views. The internal **`View`** model (from the `collab_folder` crate) is the in-memory CRDT representation optimized for collaborative editing. The `FolderManager` translates between these representations when handling UI requests.

### How does AppFlowy prevent data loss when deleting views?

Rather than immediate deletion, AppFlowy implements a **soft-delete** mechanism via **`move_view_to_trash()`** (lines 818-859 in [`manager.rs`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/manager.rs)). This function marks the target view and **all its descendants** with a trashed flag, removes them from the favorites list, and notifies observers. Trashed items remain in the database and can be restored until permanently purged by user action.

### Where is the workspace and view hierarchy state stored?

The canonical state lives inside the **`Folder`** object from the `collab_folder` crate, managed as a collab document (`CollabType::Folder`). During initialization (`FolderManager::make_folder`), this object loads from local RocksDB persistence or fetches from the cloud sync service. All hierarchy mutations modify this shared CRDT document, ensuring consistency across local storage and remote collaboration.